1 // ranges (bad, satisfactory, good) 2 // measures (actual, forecast) 3 // markers (previous, goal) 4 5 /* 6 * Chart design based on the recommendations of Stephen Few. Implementation 7 * based on the work of Clint Ivy, Jamie Love, and Jason Davies. 8 * http://projects.instantcognition.com/protovis/bulletchart/ 9 */ 10 11 /** 12 * Constructs a new, empty bullet layout. Layouts are not typically constructed 13 * directly; instead, they are added to an existing panel via 14 * {@link pv.Mark#add}. 15 * 16 * @class 17 * @extends pv.Layout 18 */ 19 pv.Layout.Bullet = function() { 20 pv.Layout.call(this); 21 var that = this, 22 buildImplied = that.buildImplied, 23 scale = that.x = pv.Scale.linear(), 24 orient, 25 horizontal, 26 rangeColor, 27 measureColor, 28 x; 29 30 /** @private Cache layout state to optimize properties. */ 31 this.buildImplied = function(s) { 32 buildImplied.call(this, x = s); 33 orient = s.orient; 34 horizontal = /^left|right$/.test(orient); 35 rangeColor = pv.ramp("#bbb", "#eee") 36 .domain(0, Math.max(1, x.ranges.length - 1)); 37 measureColor = pv.ramp("steelblue", "lightsteelblue") 38 .domain(0, Math.max(1, x.measures.length - 1)); 39 }; 40 41 /** 42 * The range prototype. 43 * 44 * @type pv.Mark 45 * @name pv.Layout.Bullet.prototype.range 46 */ 47 (this.range = new pv.Mark()) 48 .data(function() { return x.ranges; }) 49 .reverse(true) 50 .left(function() { return orient == "left" ? 0 : null; }) 51 .top(function() { return orient == "top" ? 0 : null; }) 52 .right(function() { return orient == "right" ? 0 : null; }) 53 .bottom(function() { return orient == "bottom" ? 0 : null; }) 54 .width(function(d) { return horizontal ? scale(d) : null; }) 55 .height(function(d) { return horizontal ? null : scale(d); }) 56 .fillStyle(function() { return rangeColor(this.index); }) 57 .antialias(false) 58 .parent = that; 59 60 /** 61 * The measure prototype. 62 * 63 * @type pv.Mark 64 * @name pv.Layout.Bullet.prototype.measure 65 */ 66 (this.measure = new pv.Mark()) 67 .extend(this.range) 68 .data(function() { return x.measures; }) 69 .left(function() { return orient == "left" ? 0 : horizontal ? null : this.parent.width() / 3.25; }) 70 .top(function() { return orient == "top" ? 0 : horizontal ? this.parent.height() / 3.25 : null; }) 71 .right(function() { return orient == "right" ? 0 : horizontal ? null : this.parent.width() / 3.25; }) 72 .bottom(function() { return orient == "bottom" ? 0 : horizontal ? this.parent.height() / 3.25 : null; }) 73 .fillStyle(function() { return measureColor(this.index); }) 74 .parent = that; 75 76 /** 77 * The marker prototype. 78 * 79 * @type pv.Mark 80 * @name pv.Layout.Bullet.prototype.marker 81 */ 82 (this.marker = new pv.Mark()) 83 .data(function() { return x.markers; }) 84 .left(function(d) { return orient == "left" ? scale(d) : horizontal ? null : this.parent.width() / 2; }) 85 .top(function(d) { return orient == "top" ? scale(d) : horizontal ? this.parent.height() / 2 : null; }) 86 .right(function(d) { return orient == "right" ? scale(d) : null; }) 87 .bottom(function(d) { return orient == "bottom" ? scale(d) : null; }) 88 .strokeStyle("black") 89 .shape("bar") 90 .angle(function() { return horizontal ? 0 : Math.PI / 2; }) 91 .parent = that; 92 93 (this.tick = new pv.Mark()) 94 .data(function() { return scale.ticks(7); }) 95 .left(function(d) { return orient == "left" ? scale(d) : null; }) 96 .top(function(d) { return orient == "top" ? scale(d) : null; }) 97 .right(function(d) { return orient == "right" ? scale(d) : horizontal ? null : -6; }) 98 .bottom(function(d) { return orient == "bottom" ? scale(d) : horizontal ? -8 : null; }) 99 .height(function() { return horizontal ? 6 : null; }) 100 .width(function() { return horizontal ? null : 6; }) 101 .parent = that; 102 }; 103 104 pv.Layout.Bullet.prototype = pv.extend(pv.Layout) 105 .property("orient", String) // left, right, top, bottom 106 .property("ranges") 107 .property("markers") 108 .property("measures") 109 .property("maximum", Number); 110 111 /** 112 * Default properties for bullet layouts. 113 * 114 * @type pv.Layout.Bullet 115 */ 116 pv.Layout.Bullet.prototype.defaults = new pv.Layout.Bullet() 117 .extend(pv.Layout.prototype.defaults) 118 .orient("left") 119 .ranges([]) 120 .markers([]) 121 .measures([]); 122 123 /** 124 * The orientation. 125 * 126 * @type string 127 * @name pv.Layout.Bullet.prototype.orient 128 */ 129 130 /** 131 * The array of range values. 132 * 133 * @type array 134 * @name pv.Layout.Bullet.prototype.ranges 135 */ 136 137 /** 138 * The array of marker values. 139 * 140 * @type array 141 * @name pv.Layout.Bullet.prototype.markers 142 */ 143 144 /** 145 * The array of measure values. 146 * 147 * @type array 148 * @name pv.Layout.Bullet.prototype.measures 149 */ 150 151 /** 152 * Optional; the maximum range value. 153 * 154 * @type number 155 * @name pv.Layout.Bullet.prototype.maximum 156 */ 157 158 /** @private */ 159 pv.Layout.Bullet.prototype.buildImplied = function(s) { 160 pv.Layout.prototype.buildImplied.call(this, s); 161 var size = this.parent[/^left|right$/.test(s.orient) ? "width" : "height"](); 162 s.maximum = s.maximum || pv.max([].concat(s.ranges, s.markers, s.measures)); 163 this.x.domain(0, s.maximum).range(0, size); 164 }; 165