|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('series-fill-util', function (Y, NAME) { |
|
9 |
|
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; |
|
17 |
|
18 /** |
|
19 * Utility class used for drawing area fills. |
|
20 * |
|
21 * @class Fills |
|
22 * @constructor |
|
23 * @submodule series-fill-util |
|
24 */ |
|
25 function Fills() {} |
|
26 |
|
27 Fills.ATTRS = { |
|
28 area: { |
|
29 getter: function() |
|
30 { |
|
31 return this._defaults || this._getAreaDefaults(); |
|
32 }, |
|
33 |
|
34 setter: function(val) |
|
35 { |
|
36 var defaults = this._defaults || this._getAreaDefaults(); |
|
37 this._defaults = Y.merge(defaults, val); |
|
38 } |
|
39 } |
|
40 }; |
|
41 |
|
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 }, |
|
60 |
|
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 }, |
|
75 |
|
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 }, |
|
137 |
|
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 }, |
|
195 |
|
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 } |
|
279 |
|
280 } |
|
281 path.lineTo(firstX, firstY); |
|
282 path.end(); |
|
283 }, |
|
284 |
|
285 /** |
|
286 * Storage for default area styles. |
|
287 * |
|
288 * @property _defaults |
|
289 * @type Object |
|
290 * @private |
|
291 */ |
|
292 _defaults: null, |
|
293 |
|
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 }, |
|
329 |
|
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 }, |
|
355 |
|
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 }, |
|
383 |
|
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 } |
|
416 |
|
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 } |
|
446 |
|
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 } |
|
501 |
|
502 closingXCoords.reverse(); |
|
503 closingYCoords.reverse(); |
|
504 return [xcoords.concat(closingXCoords), ycoords.concat(closingYCoords)]; |
|
505 }, |
|
506 |
|
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 } |
|
519 }; |
|
520 Y.augment(Fills, Y.Attribute); |
|
521 Y.Fills = Fills; |
|
522 |
|
523 |
|
524 }, '3.10.3'); |