Version 3.18.1
Show:

File: charts/js/Fills.js

            /**
             * Provides functionality for drawing fills in a series.
             *
             * @module charts
             * @submodule series-fill-util
             */
            var Y_Lang = Y.Lang;
            
            /**
             * Utility class used for drawing area fills.
             *
             * @class Fills
             * @constructor
             * @submodule series-fill-util
             */
            function Fills() {}
            
            Fills.ATTRS = {
                area: {
                    getter: function()
                    {
                        return this._defaults || this._getAreaDefaults();
                    },
            
                    setter: function(val)
                    {
                        var defaults = this._defaults || this._getAreaDefaults();
                        this._defaults = Y.merge(defaults, val);
                    }
                }
            };
            
            Fills.prototype = {
                /**
                 * Returns a path shape used for drawing fills.
                 *
                 * @method _getPath
                 * @return Path
                 * @private
                 */
                _getPath: function()
                {
                    var path = this._path;
                    if(!path)
                    {
                        path = this.get("graphic").addShape({type:"path"});
                        this._path = path;
                    }
                    return path;
                },
            
                /**
                 * Toggles visibility
                 *
                 * @method _toggleVisible
                 * @param {Boolean} visible indicates visibilitye
                 * @private
                 */
                _toggleVisible: function(visible)
                {
                    if(this._path)
                    {
                        this._path.set("visible", visible);
                    }
                },
            
                /**
                 * Draws fill
                 *
                 * @method drawFill
                 * @param {Array} xcoords The x-coordinates for the series.
                 * @param {Array} ycoords The y-coordinates for the series.
                 * @protected
                 */
                drawFill: function(xcoords, ycoords)
                {
                    if(xcoords.length < 1)
                    {
                        return;
                    }
                    var isNumber = Y_Lang.isNumber,
                        len = xcoords.length,
                        firstX = xcoords[0],
                        firstY = ycoords[0],
                        lastValidX = firstX,
                        lastValidY = firstY,
                        nextX,
                        nextY,
                        pointValid,
                        noPointsRendered = true,
                        i = 0,
                        styles = this.get("styles").area,
                        path = this._getPath(),
                        color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
                    path.clear();
                    path.set("fill", {
                        color: color,
                        opacity: styles.alpha
                    });
                    path.set("stroke", {weight: 0});
                    for(; i < len; i = ++i)
                    {
                        nextX = xcoords[i];
                        nextY = ycoords[i];
                        pointValid = isNumber(nextX) && isNumber(nextY);
                        if(!pointValid)
                        {
                            continue;
                        }
                        if(noPointsRendered)
                        {
                            this._firstValidX = nextX;
                            this._firstValidY = nextY;
                            noPointsRendered = false;
                            path.moveTo(nextX, nextY);
                        }
                        else
                        {
                            path.lineTo(nextX, nextY);
                        }
                        lastValidX = nextX;
                        lastValidY = nextY;
                    }
                    this._lastValidX = lastValidX;
                    this._lastValidY = lastValidY;
                    path.end();
                },
            
                /**
                 * Draws a fill for a spline
                 *
                 * @method drawAreaSpline
                 * @protected
                 */
                drawAreaSpline: function()
                {
                    if(this.get("xcoords").length < 1)
                    {
                        return;
                    }
                    var xcoords = this.get("xcoords"),
                        ycoords = this.get("ycoords"),
                        curvecoords = this.getCurveControlPoints(xcoords, ycoords),
                        len = curvecoords.length,
                        cx1,
                        cx2,
                        cy1,
                        cy2,
                        x,
                        y,
                        i = 0,
                        firstX = xcoords[0],
                        firstY = ycoords[0],
                        styles = this.get("styles").area,
                        path = this._getPath(),
                        color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
                    path.set("fill", {
                        color: color,
                        opacity: styles.alpha
                    });
                    path.set("stroke", {weight: 0});
                    path.moveTo(firstX, firstY);
                    for(; i < len; i = ++i)
                    {
                        x = curvecoords[i].endx;
                        y = curvecoords[i].endy;
                        cx1 = curvecoords[i].ctrlx1;
                        cx2 = curvecoords[i].ctrlx2;
                        cy1 = curvecoords[i].ctrly1;
                        cy2 = curvecoords[i].ctrly2;
                        path.curveTo(cx1, cy1, cx2, cy2, x, y);
                    }
                    if(this.get("direction") === "vertical")
                    {
                        path.lineTo(this._leftOrigin, y);
                        path.lineTo(this._leftOrigin, firstY);
                    }
                    else
                    {
                        path.lineTo(x, this._bottomOrigin);
                        path.lineTo(firstX, this._bottomOrigin);
                    }
                    path.lineTo(firstX, firstY);
                    path.end();
                },
            
                /**
                 * Draws a a stacked area spline
                 *
                 * @method drawStackedAreaSpline
                 * @protected
                 */
                drawStackedAreaSpline: function()
                {
                    if(this.get("xcoords").length < 1)
                    {
                        return;
                    }
                    var xcoords = this.get("xcoords"),
                        ycoords = this.get("ycoords"),
                        curvecoords,
                        order = this.get("order"),
                        seriesCollection = this.get("seriesTypeCollection"),
                        prevXCoords,
                        prevYCoords,
                        len,
                        cx1,
                        cx2,
                        cy1,
                        cy2,
                        x,
                        y,
                        i = 0,
                        firstX,
                        firstY,
                        styles = this.get("styles").area,
                        path = this._getPath(),
                        color = styles.color || this._getDefaultColor(this.get("graphOrder"), "slice");
                    firstX = xcoords[0];
                    firstY = ycoords[0];
                    curvecoords = this.getCurveControlPoints(xcoords, ycoords);
                    len = curvecoords.length;
                    path.set("fill", {
                        color: color,
                        opacity: styles.alpha
                    });
                    path.set("stroke", {weight: 0});
                    path.moveTo(firstX, firstY);
                    for(; i < len; i = ++i)
                    {
                        x = curvecoords[i].endx;
                        y = curvecoords[i].endy;
                        cx1 = curvecoords[i].ctrlx1;
                        cx2 = curvecoords[i].ctrlx2;
                        cy1 = curvecoords[i].ctrly1;
                        cy2 = curvecoords[i].ctrly2;
                        path.curveTo(cx1, cy1, cx2, cy2, x, y);
                    }
                    if(order > 0)
                    {
                        prevXCoords = seriesCollection[order - 1].get("xcoords").concat().reverse();
                        prevYCoords = seriesCollection[order - 1].get("ycoords").concat().reverse();
                        curvecoords = this.getCurveControlPoints(prevXCoords, prevYCoords);
                        i = 0;
                        len = curvecoords.length;
                        path.lineTo(prevXCoords[0], prevYCoords[0]);
                        for(; i < len; i = ++i)
                        {
                            x = curvecoords[i].endx;
                            y = curvecoords[i].endy;
                            cx1 = curvecoords[i].ctrlx1;
                            cx2 = curvecoords[i].ctrlx2;
                            cy1 = curvecoords[i].ctrly1;
                            cy2 = curvecoords[i].ctrly2;
                            path.curveTo(cx1, cy1, cx2, cy2, x, y);
                        }
                    }
                    else
                    {
                        if(this.get("direction") === "vertical")
                        {
                            path.lineTo(this._leftOrigin, ycoords[ycoords.length-1]);
                            path.lineTo(this._leftOrigin, firstY);
                        }
                        else
                        {
                            path.lineTo(xcoords[xcoords.length-1], this._bottomOrigin);
                            path.lineTo(firstX, this._bottomOrigin);
                        }
            
                    }
                    path.lineTo(firstX, firstY);
                    path.end();
                },
            
                /**
                 * Storage for default area styles.
                 *
                 * @property _defaults
                 * @type Object
                 * @private
                 */
                _defaults: null,
            
                /**
                 * Concatenates coordinate array with correct coordinates for closing an area fill.
                 *
                 * @method _getClosingPoints
                 * @return Array
                 * @protected
                 */
                _getClosingPoints: function()
                {
                    var xcoords = this.get("xcoords").concat(),
                        ycoords = this.get("ycoords").concat(),
                        firstValidIndex,
                        lastValidIndex;
                    if(this.get("direction") === "vertical")
                    {
                        lastValidIndex = this._getLastValidIndex(xcoords);
                        firstValidIndex = this._getFirstValidIndex(xcoords);
                        ycoords.push(ycoords[lastValidIndex]);
                        ycoords.push(ycoords[firstValidIndex]);
                        xcoords.push(this._leftOrigin);
                        xcoords.push(this._leftOrigin);
                    }
                    else
                    {
                        lastValidIndex = this._getLastValidIndex(ycoords);
                        firstValidIndex = this._getFirstValidIndex(ycoords);
                        xcoords.push(xcoords[lastValidIndex]);
                        xcoords.push(xcoords[firstValidIndex]);
                        ycoords.push(this._bottomOrigin);
                        ycoords.push(this._bottomOrigin);
                    }
                    xcoords.push(xcoords[0]);
                    ycoords.push(ycoords[0]);
                    return [xcoords, ycoords];
                },
            
                /**
                 * Returns the order of the series closest to the current series that has a valid value for the current index.
                 *
                 * @method _getHighestValidOrder
                 * @param {Array} seriesCollection Array of series of a given type.
                 * @param {Number} index Index of the series item.
                 * @param {Number} order Index of the the series in the seriesCollection
                 * @param {String} direction Indicates the direction of the series
                 * @return Number
                 * @private
                 */
                _getHighestValidOrder: function(seriesCollection, index, order, direction)
                {
                    var coords = direction === "vertical" ? "stackedXCoords" : "stackedYCoords",
                        coord;
                    while(isNaN(coord) && order > -1)
                    {
                      order = order - 1;
                      if(order > -1)
                      {
                        coord = seriesCollection[order].get(coords)[index];
                      }
                    }
                    return order;
                },
            
                /**
                 * Returns an array containing the x and y coordinates for a given series and index.
                 *
                 * @method _getCoordsByOrderAndIndex
                 * @param {Array} seriesCollection Array of series of a given type.
                 * @param {Number} index Index of the series item.
                 * @param {Number} order Index of the the series in the seriesCollection
                 * @param {String} direction Indicates the direction of the series
                 * @return Array
                 * @private
                 */
                _getCoordsByOrderAndIndex: function(seriesCollection, index, order, direction)
                {
                    var xcoord,
                        ycoord;
                    if(direction === "vertical")
                    {
                        xcoord = order < 0 ? this._leftOrigin : seriesCollection[order].get("stackedXCoords")[index];
                        ycoord = this.get("stackedYCoords")[index];
                    }
                    else
                    {
                        xcoord = this.get("stackedXCoords")[index];
                        ycoord = order < 0 ? this._bottomOrigin : seriesCollection[order].get("stackedYCoords")[index];
                    }
                    return [xcoord, ycoord];
                },
            
                /**
                 * Concatenates coordinate array with the correct coordinates for closing an area stack.
                 *
                 * @method _getStackedClosingPoints
                 * @return Array
                 * @protected
                 */
                _getStackedClosingPoints: function()
                {
                    var order = this.get("order"),
                        direction = this.get("direction"),
                        seriesCollection = this.get("seriesTypeCollection"),
                        firstValidIndex,
                        lastValidIndex,
                        xcoords = this.get("stackedXCoords"),
                        ycoords = this.get("stackedYCoords"),
                        limit,
                        previousSeries,
                        previousSeriesFirstValidIndex,
                        previousSeriesLastValidIndex,
                        previousXCoords,
                        previousYCoords,
                        coords,
                        closingXCoords,
                        closingYCoords,
                        currentIndex,
                        highestValidOrder,
                        oldOrder;
                    if(order < 1)
                    {
                      return this._getClosingPoints();
                    }
            
                    previousSeries = seriesCollection[order - 1];
                    previousXCoords = previousSeries.get("stackedXCoords").concat();
                    previousYCoords = previousSeries.get("stackedYCoords").concat();
                    if(direction === "vertical")
                    {
                        firstValidIndex = this._getFirstValidIndex(xcoords);
                        lastValidIndex = this._getLastValidIndex(xcoords);
                        previousSeriesFirstValidIndex = previousSeries._getFirstValidIndex(previousXCoords);
                        previousSeriesLastValidIndex = previousSeries._getLastValidIndex(previousXCoords);
                    }
                    else
                    {
                        firstValidIndex = this._getFirstValidIndex(ycoords);
                        lastValidIndex = this._getLastValidIndex(ycoords);
                        previousSeriesFirstValidIndex = previousSeries._getFirstValidIndex(previousYCoords);
                        previousSeriesLastValidIndex = previousSeries._getLastValidIndex(previousYCoords);
                    }
                    if(previousSeriesLastValidIndex >= firstValidIndex && previousSeriesFirstValidIndex <= lastValidIndex)
                    {
                        previousSeriesFirstValidIndex = Math.max(firstValidIndex, previousSeriesFirstValidIndex);
                        previousSeriesLastValidIndex = Math.min(lastValidIndex, previousSeriesLastValidIndex);
                        previousXCoords = previousXCoords.slice(previousSeriesFirstValidIndex, previousSeriesLastValidIndex + 1);
                        previousYCoords = previousYCoords.slice(previousSeriesFirstValidIndex, previousSeriesLastValidIndex + 1);
                        limit = previousSeriesFirstValidIndex;
                    }
                    else
                    {
                        limit = lastValidIndex;
                    }
            
                    closingXCoords = [xcoords[firstValidIndex]];
                    closingYCoords = [ycoords[firstValidIndex]];
                    currentIndex = firstValidIndex;
                    while((isNaN(highestValidOrder) || highestValidOrder < order - 1) && currentIndex <= limit)
                    {
                        oldOrder = highestValidOrder;
                        highestValidOrder = this._getHighestValidOrder(seriesCollection, currentIndex, order, direction);
                        if(!isNaN(oldOrder) && highestValidOrder > oldOrder)
                        {
                            coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, oldOrder, direction);
                            closingXCoords.push(coords[0]);
                            closingYCoords.push(coords[1]);
                        }
                        coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, highestValidOrder, direction);
                        closingXCoords.push(coords[0]);
                        closingYCoords.push(coords[1]);
                        currentIndex = currentIndex + 1;
                    }
                    if(previousXCoords &&
                        previousXCoords.length > 0 &&
                        previousSeriesLastValidIndex > firstValidIndex &&
                        previousSeriesFirstValidIndex < lastValidIndex)
                    {
                        closingXCoords = closingXCoords.concat(previousXCoords);
                        closingYCoords = closingYCoords.concat(previousYCoords);
                        highestValidOrder = order -1;
                    }
                    currentIndex = Math.max(firstValidIndex, previousSeriesLastValidIndex);
                    order = order - 1;
                    highestValidOrder = NaN;
                    while(currentIndex <= lastValidIndex)
                    {
                        oldOrder = highestValidOrder;
                        highestValidOrder = this._getHighestValidOrder(seriesCollection, currentIndex, order, direction);
                        if(!isNaN(oldOrder))
                        {
                            if(highestValidOrder > oldOrder)
                            {
                                coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, oldOrder, direction);
                                closingXCoords.push(coords[0]);
                                closingYCoords.push(coords[1]);
                            }
                            else if(highestValidOrder < oldOrder)
                            {
                                coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex - 1, highestValidOrder, direction);
                                closingXCoords.push(coords[0]);
                                closingYCoords.push(coords[1]);
                            }
                        }
                        coords = this._getCoordsByOrderAndIndex(seriesCollection, currentIndex, highestValidOrder, direction);
                        closingXCoords.push(coords[0]);
                        closingYCoords.push(coords[1]);
                        currentIndex = currentIndex + 1;
                    }
            
                    closingXCoords.reverse();
                    closingYCoords.reverse();
                    return [xcoords.concat(closingXCoords), ycoords.concat(closingYCoords)];
                },
            
                /**
                 * Returns default values for area styles.
                 *
                 * @method _getAreaDefaults
                 * @return Object
                 * @private
                 */
                _getAreaDefaults: function()
                {
                    return {
                    };
                }
            };
            Y.augment(Fills, Y.Attribute);
            Y.Fills = Fills;