1 /**
  2  * Constructs a new area mark with default properties. Areas 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 an area mark: the solid area between two series of
  7  * connected line segments. Unsurprisingly, areas are used most frequently for
  8  * area charts.
  9  *
 10  * <p>Just as a line represents a polyline, the <tt>Area</tt> mark type
 11  * represents a <i>polygon</i>. However, an area is not an arbitrary polygon;
 12  * vertices are paired either horizontally or vertically into parallel
 13  * <i>spans</i>, and each span corresponds to an associated datum. Either the
 14  * width or the height must be specified, but not both; this determines whether
 15  * the area is horizontally-oriented or vertically-oriented.  Like lines, areas
 16  * can be stroked and filled with arbitrary colors.
 17  *
 18  * <p>See also the <a href="../../api/Area.html">Area guide</a>.
 19  *
 20  * @extends pv.Mark
 21  */
 22 pv.Area = function() {
 23   pv.Mark.call(this);
 24 };
 25 
 26 pv.Area.prototype = pv.extend(pv.Mark)
 27     .property("width", Number)
 28     .property("height", Number)
 29     .property("lineWidth", Number)
 30     .property("strokeStyle", pv.color)
 31     .property("fillStyle", pv.color)
 32     .property("segmented", Boolean)
 33     .property("interpolate", String)
 34     .property("tension", Number);
 35 
 36 pv.Area.prototype.type = "area";
 37 
 38 /**
 39  * The width of a given span, in pixels; used for horizontal spans. If the width
 40  * is specified, the height property should be 0 (the default). Either the top
 41  * or bottom property should be used to space the spans vertically, typically as
 42  * a multiple of the index.
 43  *
 44  * @type number
 45  * @name pv.Area.prototype.width
 46  */
 47 
 48 /**
 49  * The height of a given span, in pixels; used for vertical spans. If the height
 50  * is specified, the width property should be 0 (the default). Either the left
 51  * or right property should be used to space the spans horizontally, typically
 52  * as a multiple of the index.
 53  *
 54  * @type number
 55  * @name pv.Area.prototype.height
 56  */
 57 
 58 /**
 59  * The width of stroked lines, in pixels; used in conjunction with
 60  * <tt>strokeStyle</tt> to stroke the perimeter of the area. Unlike the
 61  * {@link Line} mark type, the entire perimeter is stroked, rather than just one
 62  * edge. The default value of this property is 1.5, but since the default stroke
 63  * style is null, area marks are not stroked by default.
 64  *
 65  * <p>This property is <i>fixed</i> for non-segmented areas. See
 66  * {@link pv.Mark}.
 67  *
 68  * @type number
 69  * @name pv.Area.prototype.lineWidth
 70  */
 71 
 72 /**
 73  * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to
 74  * stroke the perimeter of the area. Unlike the {@link Line} mark type, the
 75  * entire perimeter is stroked, rather than just one edge. The default value of
 76  * this property is null, meaning areas are not stroked by default.
 77  *
 78  * <p>This property is <i>fixed</i> for non-segmented areas. See
 79  * {@link pv.Mark}.
 80  *
 81  * @type string
 82  * @name pv.Area.prototype.strokeStyle
 83  * @see pv.color
 84  */
 85 
 86 /**
 87  * The area fill style; if non-null, the interior of the polygon forming the
 88  * area is filled with the specified color. The default value of this property
 89  * is a categorical color.
 90  *
 91  * <p>This property is <i>fixed</i> for non-segmented areas. See
 92  * {@link pv.Mark}.
 93  *
 94  * @type string
 95  * @name pv.Area.prototype.fillStyle
 96  * @see pv.color
 97  */
 98 
 99 /**
100  * Whether the area is segmented; whether variations in fill style, stroke
101  * style, and the other properties are treated as fixed. Rendering segmented
102  * areas is noticeably slower than non-segmented areas.
103  *
104  * <p>This property is <i>fixed</i>. See {@link pv.Mark}.
105  *
106  * @type boolean
107  * @name pv.Area.prototype.segmented
108  */
109 
110 /**
111  * How to interpolate between values. Linear interpolation ("linear") is the
112  * default, producing a straight line between points. For piecewise constant
113  * functions (i.e., step functions), either "step-before" or "step-after" can be
114  * specified. To draw open uniform b-splines, specify "basis". To draw cardinal
115  * splines, specify "cardinal"; see also {@link #tension}.
116  *
117  * <p>This property is <i>fixed</i>. See {@link pv.Mark}.
118  *
119  * @type string
120  * @name pv.Area.prototype.interpolate
121  */
122 
123 /**
124  * The tension of cardinal splines; used in conjunction with
125  * interpolate("cardinal"). A value between 0 and 1 draws cardinal splines with
126  * the given tension. In some sense, the tension can be interpreted as the
127  * "length" of the tangent; a tension of 1 will yield all zero tangents (i.e.,
128  * linear interpolation), and a tension of 0 yields a Catmull-Rom spline. The
129  * default value is 0.7.
130  *
131  * <p>This property is <i>fixed</i>. See {@link pv.Mark}.
132  *
133  * @type number
134  * @name pv.Area.prototype.tension
135  */
136 
137 /**
138  * Default properties for areas. By default, there is no stroke and the fill
139  * style is a categorical color.
140  *
141  * @type pv.Area
142  */
143 pv.Area.prototype.defaults = new pv.Area()
144     .extend(pv.Mark.prototype.defaults)
145     .lineWidth(1.5)
146     .fillStyle(pv.Colors.category20().by(pv.parent))
147     .interpolate("linear")
148     .tension(.7);
149 
150 /** @private Sets width and height to zero if null. */
151 pv.Area.prototype.buildImplied = function(s) {
152   if (s.height == null) s.height = 0;
153   if (s.width == null) s.width = 0;
154   pv.Mark.prototype.buildImplied.call(this, s);
155 };
156 
157 /** @private Records which properties may be fixed. */
158 pv.Area.fixed = {
159   lineWidth: 1,
160   lineJoin: 1,
161   strokeStyle: 1,
162   fillStyle: 1,
163   segmented: 1,
164   interpolate: 1,
165   tension: 1
166 };
167 
168 /**
169  * @private Make segmented required, such that this fixed property is always
170  * evaluated, even if the first segment is not visible. Also cache which
171  * properties are normally fixed.
172  */
173 pv.Area.prototype.bind = function() {
174   pv.Mark.prototype.bind.call(this);
175   var binds = this.binds,
176       required = binds.required,
177       optional = binds.optional;
178   for (var i = 0, n = optional.length; i < n; i++) {
179     var p = optional[i];
180     p.fixed = p.name in pv.Area.fixed;
181     if (p.name == "segmented") {
182       required.push(p);
183       optional.splice(i, 1);
184       i--;
185       n--;
186     }
187   }
188 
189   /* Cache the original arrays so they can be restored on build. */
190   this.binds.$required = required;
191   this.binds.$optional = optional;
192 };
193 
194 /**
195  * @private Override the default build behavior such that fixed properties are
196  * determined dynamically, based on the value of the (always) fixed segmented
197  * property. Any fixed properties are only evaluated on the first instance,
198  * although their values are propagated to subsequent instances, so that they
199  * are available for property chaining and the like.
200  */
201 pv.Area.prototype.buildInstance = function(s) {
202   var binds = this.binds;
203 
204   /* Handle fixed properties on secondary instances. */
205   if (this.index) {
206     var fixed = binds.fixed;
207 
208     /* Determine which properties are fixed. */
209     if (!fixed) {
210       fixed = binds.fixed = [];
211       function f(p) { return !p.fixed || (fixed.push(p), false); }
212       binds.required = binds.required.filter(f);
213       if (!this.scene[0].segmented) binds.optional = binds.optional.filter(f);
214     }
215 
216     /* Copy fixed property values from the first instance. */
217     for (var i = 0, n = fixed.length; i < n; i++) {
218       var p = fixed[i].name;
219       s[p] = this.scene[0][p];
220     }
221   }
222 
223   /* Evaluate all properties on the first instance. */
224   else {
225     binds.required = binds.$required;
226     binds.optional = binds.$optional;
227     binds.fixed = null;
228   }
229 
230   pv.Mark.prototype.buildInstance.call(this, s);
231 };
232 
233 /**
234  * Constructs a new area anchor with default properties. Areas support five
235  * different anchors:<ul>
236  *
237  * <li>top
238  * <li>left
239  * <li>center
240  * <li>bottom
241  * <li>right
242  *
243  * </ul>In addition to positioning properties (left, right, top bottom), the
244  * anchors support text rendering properties (text-align, text-baseline). Text
245  * is rendered to appear inside the area. The area anchor also propagates the
246  * interpolate, eccentricity, and tension properties such that an anchored area
247  * or line will match positions between control points.
248  *
249  * <p>For consistency with the other mark types, the anchor positions are
250  * defined in terms of their opposite edge. For example, the top anchor defines
251  * the bottom property, such that an area added to the top anchor grows upward.
252  *
253  * @param {string} name the anchor name; either a string or a property function.
254  * @returns {pv.Anchor}
255  */
256 pv.Area.prototype.anchor = function(name) {
257   var scene;
258   return pv.Mark.prototype.anchor.call(this, name)
259     .def("$area.anchor", function() {
260         scene = this.scene.target;
261       })
262     .interpolate(function() {
263        return scene[this.index].interpolate;
264       })
265     .eccentricity(function() {
266        return scene[this.index].eccentricity;
267       })
268     .tension(function() {
269         return scene[this.index].tension;
270       });
271 };
272