1 /**
  2  * Constructs a new, empty stack layout. Layouts are not typically constructed
  3  * directly; instead, they are added to an existing panel via
  4  * {@link pv.Mark#add}.
  5  *
  6  * @class Implements a layout for stacked visualizations, ranging from simple
  7  * stacked bar charts to more elaborate "streamgraphs" composed of stacked
  8  * areas. Stack layouts uses length as a visual encoding, as opposed to
  9  * position, as the layers do not share an aligned axis.
 10  *
 11  * <p>Marks can be stacked vertically or horizontally. For example,
 12  *
 13  * <pre>vis.add(pv.Layout.Stack)
 14  *     .layers([[1, 1.2, 1.7, 1.5, 1.7],
 15  *              [.5, 1, .8, 1.1, 1.3],
 16  *              [.2, .5, .8, .9, 1]])
 17  *     .x(function() this.index * 35)
 18  *     .y(function(d) d * 40)
 19  *   .layer.add(pv.Area);</pre>
 20  *
 21  * specifies a vertically-stacked area chart, using the default "bottom-left"
 22  * orientation with "zero" offset. This visualization can be easily changed into
 23  * a streamgraph using the "wiggle" offset, which attempts to minimize change in
 24  * slope weighted by layer thickness. See the {@link #offset} property for more
 25  * supported streamgraph algorithms.
 26  *
 27  * <p>In the simplest case, the layer data can be specified as a two-dimensional
 28  * array of numbers. The <tt>x</tt> and <tt>y</tt> psuedo-properties are used to
 29  * define the thickness of each layer at the given position, respectively; in
 30  * the above example of the "bottom-left" orientation, the <tt>x</tt> and
 31  * <tt>y</tt> psuedo-properties are equivalent to the <tt>left</tt> and
 32  * <tt>height</tt> properties that you might use if you implemented a stacked
 33  * area by hand.
 34  *
 35  * <p>The advantage of using the stack layout is that the baseline, i.e., the
 36  * <tt>bottom</tt> property is computed automatically using the specified offset
 37  * algorithm. In addition, the order of layers can be computed using a built-in
 38  * algorithm via the <tt>order</tt> property.
 39  *
 40  * <p>With the exception of the "expand" <tt>offset</tt>, the stack layout does
 41  * not perform any automatic scaling of data; the values returned from
 42  * <tt>x</tt> and <tt>y</tt> specify pixel sizes. To simplify scaling math, use
 43  * this layout in conjunction with {@link pv.Scale.linear} or similar.
 44  *
 45  * <p>In other cases, the <tt>values</tt> psuedo-property can be used to define
 46  * the data more flexibly. As with a typical panel & area, the
 47  * <tt>layers</tt> property corresponds to the data in the enclosing panel,
 48  * while the <tt>values</tt> psuedo-property corresponds to the data for the
 49  * area within the panel. For example, given an array of data values:
 50  *
 51  * <pre>var crimea = [
 52  *  { date: "4/1854", wounds: 0, other: 110, disease: 110 },
 53  *  { date: "5/1854", wounds: 0, other: 95, disease: 105 },
 54  *  { date: "6/1854", wounds: 0, other: 40, disease: 95 },
 55  *  ...</pre>
 56  *
 57  * and a corresponding array of series names:
 58  *
 59  * <pre>var causes = ["wounds", "other", "disease"];</pre>
 60  *
 61  * Separate layers can be defined for each cause like so:
 62  *
 63  * <pre>vis.add(pv.Layout.Stack)
 64  *     .layers(causes)
 65  *     .values(crimea)
 66  *     .x(function(d) x(d.date))
 67  *     .y(function(d, p) y(d[p]))
 68  *   .layer.add(pv.Area)
 69  *     ...</pre>
 70  *
 71  * As with the panel & area case, the datum that is passed to the
 72  * psuedo-properties <tt>x</tt> and <tt>y</tt> are the values (an element in
 73  * <tt>crimea</tt>); the second argument is the layer data (a string in
 74  * <tt>causes</tt>). Additional arguments specify the data of enclosing panels,
 75  * if any.
 76  *
 77  * @extends pv.Layout
 78  */
 79 pv.Layout.Stack = function() {
 80   pv.Layout.call(this);
 81   var that = this,
 82       /** @ignore */ none = function() { return null; },
 83       prop = {t: none, l: none, r: none, b: none, w: none, h: none},
 84       values,
 85       buildImplied = that.buildImplied;
 86 
 87   /** @private Proxy the given property on the layer. */
 88   function proxy(name) {
 89     return function() {
 90         return prop[name](this.parent.index, this.index);
 91       };
 92   }
 93 
 94   /** @private Compute the layout! */
 95   this.buildImplied = function(s) {
 96     buildImplied.call(this, s);
 97 
 98     var data = s.layers,
 99         n = data.length,
100         m,
101         orient = s.orient,
102         horizontal = /^(top|bottom)\b/.test(orient),
103         h = this.parent[horizontal ? "height" : "width"](),
104         x = [],
105         y = [],
106         dy = [];
107 
108     /*
109      * Iterate over the data, evaluating the values, x and y functions. The
110      * context in which the x and y psuedo-properties are evaluated is a
111      * pseudo-mark that is a grandchild of this layout.
112      */
113     var stack = pv.Mark.stack, o = {parent: {parent: this}};
114     stack.unshift(null);
115     values = [];
116     for (var i = 0; i < n; i++) {
117       dy[i] = [];
118       y[i] = [];
119       o.parent.index = i;
120       stack[0] = data[i];
121       values[i] = this.$values.apply(o.parent, stack);
122       if (!i) m = values[i].length;
123       stack.unshift(null);
124       for (var j = 0; j < m; j++) {
125         stack[0] = values[i][j];
126         o.index = j;
127         if (!i) x[j] = this.$x.apply(o, stack);
128         dy[i][j] = this.$y.apply(o, stack);
129       }
130       stack.shift();
131     }
132     stack.shift();
133 
134     /* order */
135     var index;
136     switch (s.order) {
137       case "inside-out": {
138         var max = dy.map(function(v) { return pv.max.index(v); }),
139             map = pv.range(n).sort(function(a, b) { return max[a] - max[b]; }),
140             sums = dy.map(function(v) { return pv.sum(v); }),
141             top = 0,
142             bottom = 0,
143             tops = [],
144             bottoms = [];
145         for (var i = 0; i < n; i++) {
146           var j = map[i];
147           if (top < bottom) {
148             top += sums[j];
149             tops.push(j);
150           } else {
151             bottom += sums[j];
152             bottoms.push(j);
153           }
154         }
155         index = bottoms.reverse().concat(tops);
156         break;
157       }
158       case "reverse": index = pv.range(n - 1, -1, -1); break;
159       default: index = pv.range(n); break;
160     }
161 
162     /* offset */
163     switch (s.offset) {
164       case "silohouette": {
165         for (var j = 0; j < m; j++) {
166           var o = 0;
167           for (var i = 0; i < n; i++) o += dy[i][j];
168           y[index[0]][j] = (h - o) / 2;
169         }
170         break;
171       }
172       case "wiggle": {
173         var o = 0;
174         for (var i = 0; i < n; i++) o += dy[i][0];
175         y[index[0]][0] = o = (h - o) / 2;
176         for (var j = 1; j < m; j++) {
177           var s1 = 0, s2 = 0, dx = x[j] - x[j - 1];
178           for (var i = 0; i < n; i++) s1 += dy[i][j];
179           for (var i = 0; i < n; i++) {
180             var s3 = (dy[index[i]][j] - dy[index[i]][j - 1]) / (2 * dx);
181             for (var k = 0; k < i; k++) {
182               s3 += (dy[index[k]][j] - dy[index[k]][j - 1]) / dx;
183             }
184             s2 += s3 * dy[index[i]][j];
185           }
186           y[index[0]][j] = o -= s1 ? s2 / s1 * dx : 0;
187         }
188         break;
189       }
190       case "expand": {
191         for (var j = 0; j < m; j++) {
192           y[index[0]][j] = 0;
193           var k = 0;
194           for (var i = 0; i < n; i++) k += dy[i][j];
195           if (k) {
196             k = h / k;
197             for (var i = 0; i < n; i++) dy[i][j] *= k;
198           } else {
199             k = h / n;
200             for (var i = 0; i < n; i++) dy[i][j] = k;
201           }
202         }
203         break;
204       }
205       default: {
206         for (var j = 0; j < m; j++) y[index[0]][j] = 0;
207         break;
208       }
209     }
210 
211     /* Propagate the offset to the other series. */
212     for (var j = 0; j < m; j++) {
213       var o = y[index[0]][j];
214       for (var i = 1; i < n; i++) {
215         o += dy[index[i - 1]][j];
216         y[index[i]][j] = o;
217       }
218     }
219 
220     /* Find the property definitions for dynamic substitution. */
221     var i = orient.indexOf("-"),
222         pdy = horizontal ? "h" : "w",
223         px = i < 0 ? (horizontal ? "l" : "b") : orient.charAt(i + 1),
224         py = orient.charAt(0);
225     for (var p in prop) prop[p] = none;
226     prop[px] = function(i, j) { return x[j]; };
227     prop[py] = function(i, j) { return y[i][j]; };
228     prop[pdy] = function(i, j) { return dy[i][j]; };
229   };
230 
231   /**
232    * The layer prototype. This prototype is intended to be used with an area,
233    * bar or panel mark (or subclass thereof). Other mark types may be possible,
234    * though note that the stack layout is not currently designed to support
235    * radial stacked visualizations using wedges.
236    *
237    * <p>The layer is not a direct child of the stack layout; a hidden panel is
238    * used to replicate layers.
239    *
240    * @type pv.Mark
241    * @name pv.Layout.Stack.prototype.layer
242    */
243   this.layer = new pv.Mark()
244       .data(function() { return values[this.parent.index]; })
245       .top(proxy("t"))
246       .left(proxy("l"))
247       .right(proxy("r"))
248       .bottom(proxy("b"))
249       .width(proxy("w"))
250       .height(proxy("h"));
251 
252   this.layer.add = function(type) {
253     return that.add(pv.Panel)
254         .data(function() { return that.layers(); })
255       .add(type)
256         .extend(this);
257   };
258 };
259 
260 pv.Layout.Stack.prototype = pv.extend(pv.Layout)
261     .property("orient", String)
262     .property("offset", String)
263     .property("order", String)
264     .property("layers");
265 
266 /**
267  * Default properties for stack layouts. The default orientation is
268  * "bottom-left", the default offset is "zero", and the default layers is
269  * <tt>[[]]</tt>.
270  *
271  * @type pv.Layout.Stack
272  */
273 pv.Layout.Stack.prototype.defaults = new pv.Layout.Stack()
274     .extend(pv.Layout.prototype.defaults)
275     .orient("bottom-left")
276     .offset("zero")
277     .layers([[]]);
278 
279 /** @private */
280 pv.Layout.Stack.prototype.$x
281     = /** @private */ pv.Layout.Stack.prototype.$y
282     = function() { return 0; };
283 
284 /**
285  * The x psuedo-property; determines the position of the value within the layer.
286  * This typically corresponds to the independent variable. For example, with the
287  * default "bottom-left" orientation, this function defines the "left" property.
288  *
289  * @param {function} f the x function.
290  * @returns {pv.Layout.Stack} this.
291  */
292 pv.Layout.Stack.prototype.x = function(f) {
293   /** @private */ this.$x = pv.functor(f);
294   return this;
295 };
296 
297 /**
298  * The y psuedo-property; determines the thickness of the layer at the given
299  * value.  This typically corresponds to the dependent variable. For example,
300  * with the default "bottom-left" orientation, this function defines the
301  * "height" property.
302  *
303  * @param {function} f the y function.
304  * @returns {pv.Layout.Stack} this.
305  */
306 pv.Layout.Stack.prototype.y = function(f) {
307   /** @private */ this.$y = pv.functor(f);
308   return this;
309 };
310 
311 /** @private The default value function; identity. */
312 pv.Layout.Stack.prototype.$values = pv.identity;
313 
314 /**
315  * The values function; determines the values for a given layer. The default
316  * value is the identity function, which assumes that the layers property is
317  * specified as a two-dimensional (i.e., nested) array.
318  *
319  * @param {function} f the values function.
320  * @returns {pv.Layout.Stack} this.
321  */
322 pv.Layout.Stack.prototype.values = function(f) {
323   this.$values = pv.functor(f);
324   return this;
325 };
326 
327 /**
328  * The layer data in row-major order. The value of this property is typically a
329  * two-dimensional (i.e., nested) array, but any array can be used, provided the
330  * values psuedo-property is defined accordingly.
331  *
332  * @type array[]
333  * @name pv.Layout.Stack.prototype.layers
334  */
335 
336 /**
337  * The layer orientation. The following values are supported:<ul>
338  *
339  * <li>bottom-left == bottom
340  * <li>bottom-right
341  * <li>top-left == top
342  * <li>top-right
343  * <li>left-top
344  * <li>left-bottom == left
345  * <li>right-top
346  * <li>right-bottom == right
347  *
348  * </ul>. The default value is "bottom-left", which means that the layers will
349  * be built from the bottom-up, and the values within layers will be laid out
350  * from left-to-right.
351  *
352  * <p>Note that with non-zero baselines, some orientations may give similar
353  * results. For example, offset("silohouette") centers the layers, resulting in
354  * a streamgraph. Thus, the orientations "bottom-left" and "top-left" will
355  * produce similar results, differing only in the layer order.
356  *
357  * @type string
358  * @name pv.Layout.Stack.prototype.orient
359  */
360 
361 /**
362  * The layer order. The following values are supported:<ul>
363  *
364  * <li><i>null</i> - use given layer order.
365  * <li>inside-out - sort by maximum value, with balanced order.
366  * <li>reverse - use reverse of given layer order.
367  *
368  * </ul>For details on the inside-out order algorithm, refer to "Stacked Graphs
369  * -- Geometry & Aesthetics" by L. Byron and M. Wattenberg, IEEE TVCG
370  * November/December 2008.
371  *
372  * @type string
373  * @name pv.Layout.Stack.prototype.order
374  */
375 
376 /**
377  * The layer offset; the y-position of the bottom of the lowest layer. The
378  * following values are supported:<ul>
379  *
380  * <li>zero - use a zero baseline, i.e., the y-axis.
381  * <li>silohouette - center the stream, i.e., ThemeRiver.
382  * <li>wiggle - minimize weighted change in slope.
383  * <li>expand - expand layers to fill the enclosing layout dimensions.
384  *
385  * </ul>For details on these offset algorithms, refer to "Stacked Graphs --
386  * Geometry & Aesthetics" by L. Byron and M. Wattenberg, IEEE TVCG
387  * November/December 2008.
388  *
389  * @type string
390  * @name pv.Layout.Stack.prototype.offset
391  */
392