NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / series-fill-util / series-fill-util.js
blob2f5df123c6a901be14e28ac0b60f9fbe9aa00304
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('series-fill-util', function (Y, NAME) {
10 /**
11  * Provides functionality for drawing fills in a series.
12  *
13  * @module charts
14  * @submodule series-fill-util
15  */
16 var Y_Lang = Y.Lang;
18 /**
19  * Utility class used for drawing area fills.
20  *
21  * @class Fills
22  * @constructor
23  * @submodule series-fill-util
24  */
25 function Fills() {}
27 Fills.ATTRS = {
28     area: {
29         getter: function()
30         {
31             return this._defaults || this._getAreaDefaults();
32         },
34         setter: function(val)
35         {
36             var defaults = this._defaults || this._getAreaDefaults();
37             this._defaults = Y.merge(defaults, val);
38         }
39     }
42 Fills.prototype = {
43     /**
44      * Returns a path shape used for drawing fills.
45      *
46      * @method _getPath
47      * @return Path
48      * @private
49      */
50     _getPath: function()
51     {
52         var path = this._path;
53         if(!path)
54         {
55             path = this.get("graphic").addShape({type:"path"});
56             this._path = path;
57         }
58         return path;
59     },
61     /**
62      * Toggles visibility
63      *
64      * @method _toggleVisible
65      * @param {Boolean} visible indicates visibilitye
66      * @private
67      */
68     _toggleVisible: function(visible)
69     {
70         if(this._path)
71         {
72             this._path.set("visible", visible);
73         }
74     },
76     /**
77      * Draws fill
78      *
79      * @method drawFill
80      * @param {Array} xcoords The x-coordinates for the series.
81      * @param {Array} ycoords The y-coordinates for the series.
82      * @protected
83      */
84     drawFill: function(xcoords, ycoords)
85     {
86         if(xcoords.length < 1)
87         {
88             return;
89         }
90         var isNumber = Y_Lang.isNumber,
91             len = xcoords.length,
92             firstX = xcoords[0],
93             firstY = ycoords[0],
94             lastValidX = firstX,
95             lastValidY = firstY,
96             nextX,
97             nextY,
98             pointValid,
99             noPointsRendered = true,
100             i = 0,
101             styles = this.get("styles").area,
102             path = this._getPath(),
103             color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
104         path.clear();
105         path.set("fill", {
106             color: color,
107             opacity: styles.alpha
108         });
109         path.set("stroke", {weight: 0});
110         for(; i < len; i = ++i)
111         {
112             nextX = xcoords[i];
113             nextY = ycoords[i];
114             pointValid = isNumber(nextX) && isNumber(nextY);
115             if(!pointValid)
116             {
117                 continue;
118             }
119             if(noPointsRendered)
120             {
121                 this._firstValidX = nextX;
122                 this._firstValidY = nextY;
123                 noPointsRendered = false;
124                 path.moveTo(nextX, nextY);
125             }
126             else
127             {
128                 path.lineTo(nextX, nextY);
129             }
130             lastValidX = nextX;
131             lastValidY = nextY;
132         }
133         this._lastValidX = lastValidX;
134         this._lastValidY = lastValidY;
135         path.end();
136     },
138     /**
139      * Draws a fill for a spline
140      *
141      * @method drawAreaSpline
142      * @protected
143      */
144     drawAreaSpline: function()
145     {
146         if(this.get("xcoords").length < 1)
147         {
148             return;
149         }
150         var xcoords = this.get("xcoords"),
151             ycoords = this.get("ycoords"),
152             curvecoords = this.getCurveControlPoints(xcoords, ycoords),
153             len = curvecoords.length,
154             cx1,
155             cx2,
156             cy1,
157             cy2,
158             x,
159             y,
160             i = 0,
161             firstX = xcoords[0],
162             firstY = ycoords[0],
163             styles = this.get("styles").area,
164             path = this._getPath(),
165             color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
166         path.set("fill", {
167             color: color,
168             opacity: styles.alpha
169         });
170         path.set("stroke", {weight: 0});
171         path.moveTo(firstX, firstY);
172         for(; i < len; i = ++i)
173         {
174             x = curvecoords[i].endx;
175             y = curvecoords[i].endy;
176             cx1 = curvecoords[i].ctrlx1;
177             cx2 = curvecoords[i].ctrlx2;
178             cy1 = curvecoords[i].ctrly1;
179             cy2 = curvecoords[i].ctrly2;
180             path.curveTo(cx1, cy1, cx2, cy2, x, y);
181         }
182         if(this.get("direction") === "vertical")
183         {
184             path.lineTo(this._leftOrigin, y);
185             path.lineTo(this._leftOrigin, firstY);
186         }
187         else
188         {
189             path.lineTo(x, this._bottomOrigin);
190             path.lineTo(firstX, this._bottomOrigin);
191         }
192         path.lineTo(firstX, firstY);
193         path.end();
194     },
196     /**
197      * Draws a a stacked area spline
198      *
199      * @method drawStackedAreaSpline
200      * @protected
201      */
202     drawStackedAreaSpline: function()
203     {
204         if(this.get("xcoords").length < 1)
205         {
206             return;
207         }
208         var xcoords = this.get("xcoords"),
209             ycoords = this.get("ycoords"),
210             curvecoords,
211             order = this.get("order"),
212             seriesCollection = this.get("seriesTypeCollection"),
213             prevXCoords,
214             prevYCoords,
215             len,
216             cx1,
217             cx2,
218             cy1,
219             cy2,
220             x,
221             y,
222             i = 0,
223             firstX,
224             firstY,
225             styles = this.get("styles").area,
226             path = this._getPath(),
227             color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
228         firstX = xcoords[0];
229         firstY = ycoords[0];
230         curvecoords = this.getCurveControlPoints(xcoords, ycoords);
231         len = curvecoords.length;
232         path.set("fill", {
233             color: color,
234             opacity: styles.alpha
235         });
236         path.set("stroke", {weight: 0});
237         path.moveTo(firstX, firstY);
238         for(; i < len; i = ++i)
239         {
240             x = curvecoords[i].endx;
241             y = curvecoords[i].endy;
242             cx1 = curvecoords[i].ctrlx1;
243             cx2 = curvecoords[i].ctrlx2;
244             cy1 = curvecoords[i].ctrly1;
245             cy2 = curvecoords[i].ctrly2;
246             path.curveTo(cx1, cy1, cx2, cy2, x, y);
247         }
248         if(order > 0)
249         {
250             prevXCoords = seriesCollection[order - 1].get("xcoords").concat().reverse();
251             prevYCoords = seriesCollection[order - 1].get("ycoords").concat().reverse();
252             curvecoords = this.getCurveControlPoints(prevXCoords, prevYCoords);
253             i = 0;
254             len = curvecoords.length;
255             path.lineTo(prevXCoords[0], prevYCoords[0]);
256             for(; i < len; i = ++i)
257             {
258                 x = curvecoords[i].endx;
259                 y = curvecoords[i].endy;
260                 cx1 = curvecoords[i].ctrlx1;
261                 cx2 = curvecoords[i].ctrlx2;
262                 cy1 = curvecoords[i].ctrly1;
263                 cy2 = curvecoords[i].ctrly2;
264                 path.curveTo(cx1, cy1, cx2, cy2, x, y);
265             }
266         }
267         else
268         {
269             if(this.get("direction") === "vertical")
270             {
271                 path.lineTo(this._leftOrigin, ycoords[ycoords.length-1]);
272                 path.lineTo(this._leftOrigin, firstY);
273             }
274             else
275             {
276                 path.lineTo(xcoords[xcoords.length-1], this._bottomOrigin);
277                 path.lineTo(firstX, this._bottomOrigin);
278             }
280         }
281         path.lineTo(firstX, firstY);
282         path.end();
283     },
285     /**
286      * Storage for default area styles.
287      *
288      * @property _defaults
289      * @type Object
290      * @private
291      */
292     _defaults: null,
294     /**
295      * Concatenates coordinate array with correct coordinates for closing an area fill.
296      *
297      * @method _getClosingPoints
298      * @return Array
299      * @protected
300      */
301     _getClosingPoints: function()
302     {
303         var xcoords = this.get("xcoords").concat(),
304             ycoords = this.get("ycoords").concat(),
305             firstValidIndex,
306             lastValidIndex;
307         if(this.get("direction") === "vertical")
308         {
309             lastValidIndex = this._getLastValidIndex(xcoords);
310             firstValidIndex = this._getFirstValidIndex(xcoords);
311             ycoords.push(ycoords[lastValidIndex]);
312             ycoords.push(ycoords[firstValidIndex]);
313             xcoords.push(this._leftOrigin);
314             xcoords.push(this._leftOrigin);
315         }
316         else
317         {
318             lastValidIndex = this._getLastValidIndex(ycoords);
319             firstValidIndex = this._getFirstValidIndex(ycoords);
320             xcoords.push(xcoords[lastValidIndex]);
321             xcoords.push(xcoords[firstValidIndex]);
322             ycoords.push(this._bottomOrigin);
323             ycoords.push(this._bottomOrigin);
324         }
325         xcoords.push(xcoords[0]);
326         ycoords.push(ycoords[0]);
327         return [xcoords, ycoords];
328     },
330     /**
331      * Returns the order of the series closest to the current series that has a valid value for the current index.
332      *
333      * @method _getHighestValidOrder
334      * @param {Array} seriesCollection Array of series of a given type.
335      * @param {Number} index Index of the series item.
336      * @param {Number} order Index of the the series in the seriesCollection
337      * @param {String} direction Indicates the direction of the series
338      * @return Number
339      * @private
340      */
341     _getHighestValidOrder: function(seriesCollection, index, order, direction)
342     {
343         var coords = direction === "vertical" ? "stackedXCoords" : "stackedYCoords",
344             coord;
345         while(isNaN(coord) && order > -1)
346         {
347           order = order - 1;
348           if(order > -1)
349           {
350             coord = seriesCollection[order].get(coords)[index];
351           }
352         }
353         return order;
354     },
356     /**
357      * Returns an array containing the x and y coordinates for a given series and index.
358      *
359      * @method _getCoordsByOrderAndIndex
360      * @param {Array} seriesCollection Array of series of a given type.
361      * @param {Number} index Index of the series item.
362      * @param {Number} order Index of the the series in the seriesCollection
363      * @param {String} direction Indicates the direction of the series
364      * @return Array
365      * @private
366      */
367     _getCoordsByOrderAndIndex: function(seriesCollection, index, order, direction)
368     {
369         var xcoord,
370             ycoord;
371         if(direction === "vertical")
372         {
373             xcoord = order < 0 ? this._leftOrigin : seriesCollection[order].get("stackedXCoords")[index];
374             ycoord = this.get("stackedYCoords")[index];
375         }
376         else
377         {
378             xcoord = this.get("stackedXCoords")[index];
379             ycoord = order < 0 ? this._bottomOrigin : seriesCollection[order].get("stackedYCoords")[index];
380         }
381         return [xcoord, ycoord];
382     },
384     /**
385      * Concatenates coordinate array with the correct coordinates for closing an area stack.
386      *
387      * @method _getStackedClosingPoints
388      * @return Array
389      * @protected
390      */
391     _getStackedClosingPoints: function()
392     {
393         var order = this.get("order"),
394             direction = this.get("direction"),
395             seriesCollection = this.get("seriesTypeCollection"),
396             firstValidIndex,
397             lastValidIndex,
398             xcoords = this.get("stackedXCoords"),
399             ycoords = this.get("stackedYCoords"),
400             limit,
401             previousSeries,
402             previousSeriesFirstValidIndex,
403             previousSeriesLastValidIndex,
404             previousXCoords,
405             previousYCoords,
406             coords,
407             closingXCoords,
408             closingYCoords,
409             currentIndex,
410             highestValidOrder,
411             oldOrder;
412         if(order < 1)
413         {
414           return this._getClosingPoints();
415         }
417         previousSeries = seriesCollection[order - 1];
418         previousXCoords = previousSeries.get("stackedXCoords").concat();
419         previousYCoords = previousSeries.get("stackedYCoords").concat();
420         if(direction === "vertical")
421         {
422             firstValidIndex = this._getFirstValidIndex(xcoords);
423             lastValidIndex = this._getLastValidIndex(xcoords);
424             previousSeriesFirstValidIndex = previousSeries._getFirstValidIndex(previousXCoords);
425             previousSeriesLastValidIndex = previousSeries._getLastValidIndex(previousXCoords);
426         }
427         else
428         {
429             firstValidIndex = this._getFirstValidIndex(ycoords);
430             lastValidIndex = this._getLastValidIndex(ycoords);
431             previousSeriesFirstValidIndex = previousSeries._getFirstValidIndex(previousYCoords);
432             previousSeriesLastValidIndex = previousSeries._getLastValidIndex(previousYCoords);
433         }
434         if(previousSeriesLastValidIndex >= firstValidIndex && previousSeriesFirstValidIndex <= lastValidIndex)
435         {
436             previousSeriesFirstValidIndex = Math.max(firstValidIndex, previousSeriesFirstValidIndex);
437             previousSeriesLastValidIndex = Math.min(lastValidIndex, previousSeriesLastValidIndex);
438             previousXCoords = previousXCoords.slice(previousSeriesFirstValidIndex, previousSeriesLastValidIndex + 1);
439             previousYCoords = previousYCoords.slice(previousSeriesFirstValidIndex, previousSeriesLastValidIndex + 1);
440             limit = previousSeriesFirstValidIndex;
441         }
442         else
443         {
444             limit = lastValidIndex;
445         }
447         closingXCoords = [xcoords[firstValidIndex]];
448         closingYCoords = [ycoords[firstValidIndex]];
449         currentIndex = firstValidIndex;
450         while((isNaN(highestValidOrder) || highestValidOrder < order - 1) && currentIndex <= limit)
451         {
452             oldOrder = highestValidOrder;
453             highestValidOrder = this._getHighestValidOrder(seriesCollection, currentIndex, order, direction);
454             if(!isNaN(oldOrder) && highestValidOrder > oldOrder)
455             {
456                 coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, oldOrder, direction);
457                 closingXCoords.push(coords[0]);
458                 closingYCoords.push(coords[1]);
459             }
460             coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, highestValidOrder, direction);
461             closingXCoords.push(coords[0]);
462             closingYCoords.push(coords[1]);
463             currentIndex = currentIndex + 1;
464         }
465         if(previousXCoords &&
466             previousXCoords.length > 0 &&
467             previousSeriesLastValidIndex > firstValidIndex &&
468             previousSeriesFirstValidIndex < lastValidIndex)
469         {
470             closingXCoords = closingXCoords.concat(previousXCoords);
471             closingYCoords = closingYCoords.concat(previousYCoords);
472             highestValidOrder = order -1;
473         }
474         currentIndex = Math.max(firstValidIndex, previousSeriesLastValidIndex);
475         order = order - 1;
476         highestValidOrder = NaN;
477         while(currentIndex <= lastValidIndex)
478         {
479             oldOrder = highestValidOrder;
480             highestValidOrder = this._getHighestValidOrder(seriesCollection, currentIndex, order, direction);
481             if(!isNaN(oldOrder))
482             {
483                 if(highestValidOrder > oldOrder)
484                 {
485                     coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, oldOrder, direction);
486                     closingXCoords.push(coords[0]);
487                     closingYCoords.push(coords[1]);
488                 }
489                 else if(highestValidOrder < oldOrder)
490                 {
491                     coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex - 1, highestValidOrder, direction);
492                     closingXCoords.push(coords[0]);
493                     closingYCoords.push(coords[1]);
494                 }
495             }
496             coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, highestValidOrder, direction);
497             closingXCoords.push(coords[0]);
498             closingYCoords.push(coords[1]);
499             currentIndex = currentIndex + 1;
500         }
502         closingXCoords.reverse();
503         closingYCoords.reverse();
504         return [xcoords.concat(closingXCoords), ycoords.concat(closingYCoords)];
505     },
507     /**
508      * Returns default values for area styles.
509      *
510      * @method _getAreaDefaults
511      * @return Object
512      * @private
513      */
514     _getAreaDefaults: function()
515     {
516         return {
517         };
518     }
520 Y.augment(Fills, Y.Attribute);
521 Y.Fills = Fills;
524 }, '3.13.0');