1 /** 2 * Constructs a new wedge with default properties. Wedges are not typically 3 * constructed directly, but by adding to a panel or an existing mark via 4 * {@link pv.Mark#add}. 5 * 6 * @class Represents a wedge, or pie slice. Specified in terms of start and end 7 * angle, inner and outer radius, wedges can be used to construct donut charts 8 * and polar bar charts as well. If the {@link #angle} property is used, the end 9 * angle is implied by adding this value to start angle. By default, the start 10 * angle is the previously-generated wedge's end angle. This design allows 11 * explicit control over the wedge placement if desired, while offering 12 * convenient defaults for the construction of radial graphs. 13 * 14 * <p>The center point of the circle is positioned using the standard box model. 15 * The wedge can be stroked and filled, similar to {link Bar}. 16 * 17 * <p>See also the <a href="../../api/Wedge.html">Wedge guide</a>. 18 * 19 * @extends pv.Mark 20 */ 21 pv.Wedge = function() { 22 pv.Mark.call(this); 23 }; 24 25 pv.Wedge.prototype = pv.extend(pv.Mark) 26 .property("startAngle") 27 .property("endAngle") 28 .property("angle") 29 .property("innerRadius") 30 .property("outerRadius") 31 .property("lineWidth") 32 .property("strokeStyle") 33 .property("fillStyle"); 34 35 pv.Wedge.prototype.type = "wedge"; 36 37 /** 38 * The start angle of the wedge, in radians. The start angle is measured 39 * clockwise from the 3 o'clock position. The default value of this property is 40 * the end angle of the previous instance (the {@link Mark#sibling}), or -PI / 2 41 * for the first wedge; for pie and donut charts, typically only the 42 * {@link #angle} property needs to be specified. 43 * 44 * @type number 45 * @name pv.Wedge.prototype.startAngle 46 */ 47 48 /** 49 * The end angle of the wedge, in radians. If not specified, the end angle is 50 * implied as the start angle plus the {@link #angle}. 51 * 52 * @type number 53 * @name pv.Wedge.prototype.endAngle 54 */ 55 56 /** 57 * The angular span of the wedge, in radians. This property is used if end angle 58 * is not specified. 59 * 60 * @type number 61 * @name pv.Wedge.prototype.angle 62 */ 63 64 /** 65 * The inner radius of the wedge, in pixels. The default value of this property 66 * is zero; a positive value will produce a donut slice rather than a pie slice. 67 * The inner radius can vary per-wedge. 68 * 69 * @type number 70 * @name pv.Wedge.prototype.innerRadius 71 */ 72 73 /** 74 * The outer radius of the wedge, in pixels. This property is required. For 75 * pies, only this radius is required; for donuts, the inner radius must be 76 * specified as well. The outer radius can vary per-wedge. 77 * 78 * @type number 79 * @name pv.Wedge.prototype.outerRadius 80 */ 81 82 /** 83 * The width of stroked lines, in pixels; used in conjunction with 84 * <tt>strokeStyle</tt> to stroke the wedge's border. 85 * 86 * @type number 87 * @name pv.Wedge.prototype.lineWidth 88 */ 89 90 /** 91 * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to 92 * stroke the wedge's border. The default value of this property is null, 93 * meaning wedges are not stroked by default. 94 * 95 * @type string 96 * @name pv.Wedge.prototype.strokeStyle 97 * @see pv.color 98 */ 99 100 /** 101 * The wedge fill style; if non-null, the interior of the wedge is filled with 102 * the specified color. The default value of this property is a categorical 103 * color. 104 * 105 * @type string 106 * @name pv.Wedge.prototype.fillStyle 107 * @see pv.color 108 */ 109 110 /** 111 * Default properties for wedges. By default, there is no stroke and the fill 112 * style is a categorical color. 113 * 114 * @type pv.Wedge 115 */ 116 pv.Wedge.prototype.defaults = new pv.Wedge() 117 .extend(pv.Mark.prototype.defaults) 118 .startAngle(function() { 119 var s = this.sibling(); 120 return s ? s.endAngle : -Math.PI / 2; 121 }) 122 .innerRadius(0) 123 .lineWidth(1.5) 124 .strokeStyle(null) 125 .fillStyle(defaultFillStyle.by(pv.index)); 126 127 /** 128 * Returns the mid-radius of the wedge, which is defined as half-way between the 129 * inner and outer radii. 130 * 131 * @see #innerRadius 132 * @see #outerRadius 133 * @returns {number} the mid-radius, in pixels. 134 */ 135 pv.Wedge.prototype.midRadius = function() { 136 return (this.innerRadius() + this.outerRadius()) / 2; 137 }; 138 139 /** 140 * Returns the mid-angle of the wedge, which is defined as half-way between the 141 * start and end angles. 142 * 143 * @see #startAngle 144 * @see #endAngle 145 * @returns {number} the mid-angle, in radians. 146 */ 147 pv.Wedge.prototype.midAngle = function() { 148 return (this.startAngle() + this.endAngle()) / 2; 149 }; 150 151 /** 152 * Constructs a new wedge anchor with default properties. Wedges support five 153 * different anchors:<ul> 154 * 155 * <li>outer 156 * <li>inner 157 * <li>center 158 * <li>start 159 * <li>end 160 * 161 * </ul>In addition to positioning properties (left, right, top bottom), the 162 * anchors support text rendering properties (text-align, text-baseline, 163 * textAngle). Text is rendered to appear inside the wedge. 164 * 165 * @param {string} name the anchor name; either a string or a property function. 166 * @returns {pv.Anchor} 167 */ 168 pv.Wedge.prototype.anchor = function(name) { 169 var w = this; 170 return pv.Mark.prototype.anchor.call(this, name) 171 .left(function() { 172 switch (this.name()) { 173 case "outer": return w.left() + w.outerRadius() * Math.cos(w.midAngle()); 174 case "inner": return w.left() + w.innerRadius() * Math.cos(w.midAngle()); 175 case "start": return w.left() + w.midRadius() * Math.cos(w.startAngle()); 176 case "center": return w.left() + w.midRadius() * Math.cos(w.midAngle()); 177 case "end": return w.left() + w.midRadius() * Math.cos(w.endAngle()); 178 } 179 }) 180 .right(function() { 181 switch (this.name()) { 182 case "outer": return w.right() + w.outerRadius() * Math.cos(w.midAngle()); 183 case "inner": return w.right() + w.innerRadius() * Math.cos(w.midAngle()); 184 case "start": return w.right() + w.midRadius() * Math.cos(w.startAngle()); 185 case "center": return w.right() + w.midRadius() * Math.cos(w.midAngle()); 186 case "end": return w.right() + w.midRadius() * Math.cos(w.endAngle()); 187 } 188 }) 189 .top(function() { 190 switch (this.name()) { 191 case "outer": return w.top() + w.outerRadius() * Math.sin(w.midAngle()); 192 case "inner": return w.top() + w.innerRadius() * Math.sin(w.midAngle()); 193 case "start": return w.top() + w.midRadius() * Math.sin(w.startAngle()); 194 case "center": return w.top() + w.midRadius() * Math.sin(w.midAngle()); 195 case "end": return w.top() + w.midRadius() * Math.sin(w.endAngle()); 196 } 197 }) 198 .bottom(function() { 199 switch (this.name()) { 200 case "outer": return w.bottom() + w.outerRadius() * Math.sin(w.midAngle()); 201 case "inner": return w.bottom() + w.innerRadius() * Math.sin(w.midAngle()); 202 case "start": return w.bottom() + w.midRadius() * Math.sin(w.startAngle()); 203 case "center": return w.bottom() + w.midRadius() * Math.sin(w.midAngle()); 204 case "end": return w.bottom() + w.midRadius() * Math.sin(w.endAngle()); 205 } 206 }) 207 .textAlign(function() { 208 switch (this.name()) { 209 case "outer": return pv.Wedge.upright(w.midAngle()) ? "right" : "left"; 210 case "inner": return pv.Wedge.upright(w.midAngle()) ? "left" : "right"; 211 } 212 return "center"; 213 }) 214 .textBaseline(function() { 215 switch (this.name()) { 216 case "start": return pv.Wedge.upright(w.startAngle()) ? "top" : "bottom"; 217 case "end": return pv.Wedge.upright(w.endAngle()) ? "bottom" : "top"; 218 } 219 return "middle"; 220 }) 221 .textAngle(function() { 222 var a = 0; 223 switch (this.name()) { 224 case "center": 225 case "inner": 226 case "outer": a = w.midAngle(); break; 227 case "start": a = w.startAngle(); break; 228 case "end": a = w.endAngle(); break; 229 } 230 return pv.Wedge.upright(a) ? a : (a + Math.PI); 231 }); 232 }; 233 234 /** 235 * Returns true if the specified angle is considered "upright", as in, text 236 * rendered at that angle would appear upright. If the angle is not upright, 237 * text is rotated 180 degrees to be upright, and the text alignment properties 238 * are correspondingly changed. 239 * 240 * @param {number} angle an angle, in radius. 241 * @returns {boolean} true if the specified angle is upright. 242 */ 243 pv.Wedge.upright = function(angle) { 244 angle = angle % (2 * Math.PI); 245 angle = (angle < 0) ? (2 * Math.PI + angle) : angle; 246 return (angle < Math.PI / 2) || (angle > 3 * Math.PI / 2); 247 }; 248 249 /** 250 * @private Overrides the default behavior of {@link pv.Mark.buildImplied} such 251 * that the end angle is computed from the start angle and angle (angular span) 252 * if not specified. 253 * 254 * @param s a node in the scene graph; the instance of the wedge to build. 255 */ 256 pv.Wedge.prototype.buildImplied = function(s) { 257 pv.Mark.prototype.buildImplied.call(this, s); 258 259 /* 260 * TODO If the angle or endAngle is updated by an event handler, the implied 261 * properties won't recompute correctly, so this will lead to potentially 262 * buggy redraw. How to re-evaluate implied properties on update? 263 */ 264 if (s.endAngle == null) s.endAngle = s.startAngle + s.angle; 265 if (s.angle == null) s.angle = s.endAngle - s.startAngle; 266 }; 267