#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: charts/js/ChartLegend.js

    /**
     * ChartLegend provides a legend for a chart.
     *
     * @class ChartLegend
     * @module charts
     * @submodule charts-legend
     * @extends Widget
     */
    Y.ChartLegend = Y.Base.create("chartlegend", Y.Widget, [Y.Renderer], {
        /**
         * Initializes the chart.
         *
         * @method initializer
         * @private
         */
        initializer: function()
        {
            this._items = [];
        },

        /**
         * @method renderUI
         * @private
         */
        renderUI: function()
        {
            var bb = this.get("boundingBox"),
                cb = this.get("contentBox"),
                styles = this.get("styles").background,
                background = new Y.Rect({
                    graphic: cb,
                    fill: styles.fill,
                    stroke: styles.border
                });
            bb.setStyle("display", "block");
            bb.setStyle("position", "absolute");
            this.set("background", background);
        },

        /**
         * @method bindUI
         * @private
         */
        bindUI: function()
        {
            this.get("chart").after("seriesCollectionChange", Y.bind(this._updateHandler, this));
            this.get("chart").after("stylesChange", Y.bind(this._updateHandler, this));
            this.after("stylesChange", this._updateHandler);
            this.after("positionChange", this._positionChangeHandler);
            this.after("widthChange", this._handleSizeChange);
            this.after("heightChange", this._handleSizeChange);
        },

        /**
         * @method syncUI
         * @private
         */
        syncUI: function()
        {
            var w = this.get("width"),
                h = this.get("height");
            if(isFinite(w) && isFinite(h) && w > 0 && h > 0)
            {
                this._drawLegend();
            }
        },

        /**
         * Handles changes to legend.
         *
         * @method _updateHandler
         * @param {Object} e Event object
         * @private
         */
        _updateHandler: function()
        {
            if(this.get("rendered"))
            {
                this._drawLegend();
            }
        },

        /**
         * Handles position changes.
         *
         * @method _positionChangeHandler
         * @param {Object} e Event object
         * @private
         */
        _positionChangeHandler: function()
        {
            var chart = this.get("chart"),
                parentNode = this._parentNode;
            if(parentNode && ((chart && this.get("includeInChartLayout"))))
            {
                this.fire("legendRendered");
            }
            else if(this.get("rendered"))
            {
                this._drawLegend();
            }
        },

        /**
         * Updates the legend when the size changes.
         *
         * @method _handleSizeChange
         * @param {Object} e Event object.
         * @private
         */
        _handleSizeChange: function(e)
        {
            var attrName = e.attrName,
                pos = this.get(POSITION),
                vert = pos === LEFT || pos === RIGHT,
                hor = pos === BOTTOM || pos === TOP;
            if((hor && attrName === WIDTH) || (vert && attrName === HEIGHT))
            {
                this._drawLegend();
            }
        },

        /**
         * Draws the legend
         *
         * @method _drawLegend
         * @private
         */
        _drawLegend: function()
        {
            if(this._drawing)
            {
                this._callLater = true;
                return;
            }
            this._drawing = true;
            this._callLater = false;
            if(this.get("includeInChartLayout"))
            {
                this.get("chart")._itemRenderQueue.unshift(this);
            }
            var chart = this.get("chart"),
                node = this.get("contentBox"),
                seriesCollection = chart.get("seriesCollection"),
                series,
                styles = this.get("styles"),
                padding = styles.padding,
                itemStyles = styles.item,
                seriesStyles,
                hSpacing = itemStyles.hSpacing,
                vSpacing = itemStyles.vSpacing,
                direction = this.get("direction"),
                align = direction === "vertical" ? styles.vAlign : styles.hAlign,
                marker = styles.marker,
                labelStyles = itemStyles.label,
                displayName,
                layout = this._layout[direction],
                i,
                len,
                isArray,
                legendShape,
                shape,
                shapeClass,
                item,
                fill,
                border,
                fillColors,
                borderColors,
                borderWeight,
                items = [],
                markerWidth = marker.width,
                markerHeight = marker.height,
                totalWidth = 0 - hSpacing,
                totalHeight = 0 - vSpacing,
                maxWidth = 0,
                maxHeight = 0,
                itemWidth,
                itemHeight;
            if(marker && marker.shape)
            {
                legendShape = marker.shape;
            }
            this._destroyLegendItems();
            if(chart instanceof Y.PieChart)
            {
                series = seriesCollection[0];
                displayName = series.get("categoryAxis").getDataByKey(series.get("categoryKey"));
                seriesStyles = series.get("styles").marker;
                fillColors = seriesStyles.fill.colors;
                borderColors = seriesStyles.border.colors;
                borderWeight = seriesStyles.border.weight;
                i = 0;
                len = displayName.length;
                shape = legendShape || Y.Circle;
                isArray = Y.Lang.isArray(shape);
                for(; i < len; ++i)
                {
                    shape = isArray ? shape[i] : shape;
                    fill = {
                        color: fillColors[i]
                    };
                    border = {
                        colors: borderColors[i],
                        weight: borderWeight
                    };
                    displayName = chart.getSeriesItems(series, i).category.value;
                    item = this._getLegendItem(node, this._getShapeClass(shape), fill, border, labelStyles, markerWidth, markerHeight, displayName);
                    itemWidth = item.width;
                    itemHeight = item.height;
                    maxWidth = Math.max(maxWidth, itemWidth);
                    maxHeight = Math.max(maxHeight, itemHeight);
                    totalWidth += itemWidth + hSpacing;
                    totalHeight += itemHeight + vSpacing;
                    items.push(item);
                }
            }
            else
            {
                i = 0;
                len = seriesCollection.length;
                for(; i < len; ++i)
                {
                    series = seriesCollection[i];
                    seriesStyles = this._getStylesBySeriesType(series, shape);
                    if(!legendShape)
                    {
                        shape = seriesStyles.shape;
                        if(!shape)
                        {
                            shape = Y.Circle;
                        }
                    }
                    shapeClass = Y.Lang.isArray(shape) ? shape[i] : shape;
                    item = this._getLegendItem(
                        node,
                        this._getShapeClass(shape),
                        seriesStyles.fill,
                        seriesStyles.border,
                        labelStyles,
                        markerWidth,
                        markerHeight,
                        series.get("valueDisplayName")
                    );
                    itemWidth = item.width;
                    itemHeight = item.height;
                    maxWidth = Math.max(maxWidth, itemWidth);
                    maxHeight = Math.max(maxHeight, itemHeight);
                    totalWidth += itemWidth + hSpacing;
                    totalHeight += itemHeight + vSpacing;
                    items.push(item);
                }
            }
            this._drawing = false;
            if(this._callLater)
            {
                this._drawLegend();
            }
            else
            {
                layout._positionLegendItems.apply(
                    this,
                    [items, maxWidth, maxHeight, totalWidth, totalHeight, padding, hSpacing, vSpacing, align]
                );
                this._updateBackground(styles);
                this.fire("legendRendered");
            }
        },

        /**
         * Updates the background for the legend.
         *
         * @method _updateBackground
         * @param {Object} styles Reference to the legend's styles attribute
         * @private
         */
        _updateBackground: function(styles)
        {
            var backgroundStyles = styles.background,
                contentRect = this._contentRect,
                padding = styles.padding,
                x = contentRect.left - padding.left,
                y = contentRect.top - padding.top,
                w = contentRect.right - x + padding.right,
                h = contentRect.bottom - y + padding.bottom;
            this.get("background").set({
                fill: backgroundStyles.fill,
                stroke: backgroundStyles.border,
                width: w,
                height: h,
                x: x,
                y: y
            });
        },

        /**
         * Retrieves the marker styles based on the type of series. For series that contain a marker, the marker styles are returned.
         *
         * @method _getStylesBySeriesType
         * @param {CartesianSeries | PieSeries} The series in which the style properties will be received.
         * @return Object An object containing fill, border and shape information.
         * @private
         */
        _getStylesBySeriesType: function(series)
        {
            var styles = series.get("styles"),
                color;
            if(series instanceof Y.LineSeries || series instanceof Y.StackedLineSeries)
            {
                styles = series.get("styles").line;
                color = styles.color || series._getDefaultColor(series.get("graphOrder"), "line");
                return {
                    border: {
                        weight: 1,
                        color: color
                    },
                    fill: {
                        color: color
                    }
                };
            }
            else if(series instanceof Y.AreaSeries || series instanceof Y.StackedAreaSeries)
            {
                styles = series.get("styles").area;
                color = styles.color || series._getDefaultColor(series.get("graphOrder"), "slice");
                return {
                    border: {
                        weight: 1,
                        color: color
                    },
                    fill: {
                        color: color
                    }
                };
            }
            else
            {
                styles = series.get("styles").marker;
                return {
                    fill: styles.fill,

                    border: {
                        weight: styles.border.weight,

                        color: styles.border.color,

                        shape: styles.shape
                    },
                    shape: styles.shape
                };
            }
        },

        /**
         * Returns a legend item consisting of the following properties:
         *  <dl>
         *    <dt>node</dt><dd>The `Node` containing the legend item elements.</dd>
         *      <dt>shape</dt><dd>The `Shape` element for the legend item.</dd>
         *      <dt>textNode</dt><dd>The `Node` containing the text></dd>
         *      <dt>text</dt><dd></dd>
         *  </dl>
         *
         * @method _getLegendItem
         * @param {Node} shapeProps Reference to the `node` attribute.
         * @param {String | Class} shapeClass The type of shape
         * @param {Object} fill Properties for the shape's fill
         * @param {Object} border Properties for the shape's border
         * @param {String} text String to be rendered as the legend's text
         * @param {Number} width Total width of the legend item
         * @param {Number} height Total height of the legend item
         * @param {HTML | String} text Text for the legendItem
         * @return Object
         * @private
         */
        _getLegendItem: function(node, shapeClass, fill, border, labelStyles, w, h, text)
        {
            var containerNode = Y.one(DOCUMENT.createElement("div")),
                textField = Y.one(DOCUMENT.createElement("span")),
                shape,
                dimension,
                padding,
                left,
                item,
                ShapeClass = shapeClass;
            containerNode.setStyle(POSITION, "absolute");
            textField.setStyle(POSITION, "absolute");
            textField.setStyles(labelStyles);
            textField.appendChild(DOCUMENT.createTextNode(text));
            containerNode.appendChild(textField);
            node.appendChild(containerNode);
            dimension = textField.get("offsetHeight");
            padding = dimension - h;
            left = w + padding + 2;
            textField.setStyle("left", left + PX);
            containerNode.setStyle("height", dimension + PX);
            containerNode.setStyle("width", (left + textField.get("offsetWidth")) + PX);
            shape = new ShapeClass({
                fill: fill,
                stroke: border,
                width: w,
                height: h,
                x: padding * 0.5,
                y: padding * 0.5,
                w: w,
                h: h,
                graphic: containerNode
            });
            textField.setStyle("left", dimension + PX);
            item = {
                node: containerNode,
                width: containerNode.get("offsetWidth"),
                height: containerNode.get("offsetHeight"),
                shape: shape,
                textNode: textField,
                text: text
            };
            this._items.push(item);
            return item;
        },

        /**
         * Evaluates and returns correct class for drawing a shape.
         *
         * @method _getShapeClass
         * @return Shape
         * @private
         */
        _getShapeClass: function()
        {
            var graphic = this.get("background").get("graphic");
            return graphic._getShapeClass.apply(graphic, arguments);
        },

        /**
         * Returns the default hash for the `styles` attribute.
         *
         * @method _getDefaultStyles
         * @return Object
         * @protected
         */
        _getDefaultStyles: function()
        {
            var styles = {
                padding: {
                    top: 8,
                    right: 8,
                    bottom: 8,
                    left: 9
                },
                gap: 10,
                hAlign: "center",
                vAlign: "top",
                marker: this._getPlotDefaults(),
                item: {
                    hSpacing: 10,
                    vSpacing: 5,
                    label: {
                        color:"#808080",
                        fontSize:"85%",
                        whiteSpace: "nowrap"
                    }
                },
                background: {
                    shape: "rect",
                    fill:{
                        color:"#faf9f2"
                    },
                    border: {
                        color:"#dad8c9",
                        weight: 1
                    }
                }
            };
            return styles;
        },

        /**
         * Gets the default values for series that use the utility. This method is used by
         * the class' `styles` attribute's getter to get build default values.
         *
         * @method _getPlotDefaults
         * @return Object
         * @protected
         */
        _getPlotDefaults: function()
        {
            var defs = {
                width: 10,
                height: 10
            };
            return defs;
        },

        /**
         * Destroys legend items.
         *
         * @method _destroyLegendItems
         * @private
         */
        _destroyLegendItems: function()
        {
            var item;
            if(this._items)
            {
                while(this._items.length > 0)
                {
                    item = this._items.shift();
                    item.shape.get("graphic").destroy();
                    item.node.empty();
                    item.node.destroy(true);
                    item.node = null;
                    item = null;
                }
            }
            this._items = [];
        },

        /**
         * Maps layout classes.
         *
         * @property _layout
         * @private
         */
        _layout: {
            vertical: VerticalLegendLayout,
            horizontal: HorizontalLegendLayout
        },

        /**
         * Destructor implementation ChartLegend class. Removes all items and the Graphic instance from the widget.
         *
         * @method destructor
         * @protected
         */
        destructor: function()
        {
            var background = this.get("background"),
                backgroundGraphic;
            this._destroyLegendItems();
            if(background)
            {
                backgroundGraphic = background.get("graphic");
                if(backgroundGraphic)
                {
                    backgroundGraphic.destroy();
                }
                else
                {
                    background.destroy();
                }
            }

        }
    }, {
        ATTRS: {
            /**
             * Indicates whether the chart's contentBox is the parentNode for the legend.
             *
             * @attribute includeInChartLayout
             * @type Boolean
             * @private
             */
            includeInChartLayout: {
                value: false
            },

            /**
             * Reference to the `Chart` instance.
             *
             * @attribute chart
             * @type Chart
             */
            chart: {
                setter: function(val)
                {
                    this.after("legendRendered", Y.bind(val._itemRendered, val));
                    return val;
                }
            },

            /**
             * Indicates the direction in relation of the legend's layout. The `direction` of the legend is determined by its
             * `position` value.
             *
             * @attribute direction
             * @type String
             */
            direction: {
                value: "vertical"
            },

            /**
             * Indicates the position and direction of the legend. Possible values are `left`, `top`, `right` and `bottom`.
             * Values of `left` and `right` values have a `direction` of `vertical`. Values of `top` and `bottom` values have
             * a `direction` of `horizontal`.
             *
             * @attribute position
             * @type String
             */
            position: {
                lazyAdd: false,

                value: "right",

                setter: function(val)
                {
                    if(val === TOP || val === BOTTOM)
                    {
                        this.set("direction", HORIZONTAL);
                    }
                    else if(val === LEFT || val === RIGHT)
                    {
                        this.set("direction", VERTICAL);
                    }
                    return val;
                }
            },

            /**
             * The width of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
             * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
             * `width` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
             * determined by the direction of the legend. If the `position` is `left` or `right` or the `direction` is
             * `vertical`, width is `readOnly`. If the position is `top` or `bottom` or the `direction` is `horizontal`,
             * width can be explicitly set. If width is not explicitly set, the width will be determined by the width of the
             * legend's parent element.
             *
             * @attribute width
             * @type Number
             */
            width: {
                getter: function()
                {
                    var chart = this.get("chart"),
                        parentNode = this._parentNode;
                    if(parentNode)
                    {
                        if((chart && this.get("includeInChartLayout")) || this._width)
                        {
                            if(!this._width)
                            {
                                this._width = 0;
                            }
                            return this._width;
                        }
                        else
                        {
                            return parentNode.get("offsetWidth");
                        }
                    }
                    return "";
                },

                setter: function(val)
                {
                    this._width = val;
                    return val;
                }
            },

            /**
             * The height of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
             * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
             * `height` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
             * determined by the direction of the legend. If the `position` is `top` or `bottom` or the `direction` is
             * `horizontal`, height is `readOnly`. If the position is `left` or `right` or the `direction` is `vertical`,
             * height can be explicitly set. If height is not explicitly set, the height will be determined by the width of the
             * legend's parent element.
             *
             * @attribute height
             * @type Number
             */
            height: {
                valueFn: "_heightGetter",

                getter: function()
                {
                    var chart = this.get("chart"),
                        parentNode = this._parentNode;
                    if(parentNode)
                    {
                        if((chart && this.get("includeInChartLayout")) || this._height)
                        {
                            if(!this._height)
                            {
                                this._height = 0;
                            }
                            return this._height;
                        }
                        else
                        {
                            return parentNode.get("offsetHeight");
                        }
                    }
                    return "";
                },

                setter: function(val)
                {
                    this._height = val;
                    return val;
                }
            },

            /**
             * Indicates the x position of legend.
             *
             * @attribute x
             * @type Number
             * @readOnly
             */
            x: {
                lazyAdd: false,

                value: 0,

                setter: function(val)
                {
                    var node = this.get("boundingBox");
                    if(node)
                    {
                        node.setStyle(LEFT, val + PX);
                    }
                    return val;
                }
            },

            /**
             * Indicates the y position of legend.
             *
             * @attribute y
             * @type Number
             * @readOnly
             */
            y: {
                lazyAdd: false,

                value: 0,

                setter: function(val)
                {
                    var node = this.get("boundingBox");
                    if(node)
                    {
                        node.setStyle(TOP, val + PX);
                    }
                    return val;
                }
            },

            /**
             * Array of items contained in the legend. Each item is an object containing the following properties:
             *
             * <dl>
             *      <dt>node</dt><dd>Node containing text for the legend item.</dd>
             *      <dt>marker</dt><dd>Shape for the legend item.</dd>
             * </dl>
             *
             * @attribute items
             * @type Array
             * @readOnly
             */
            items: {
                getter: function()
                {
                    return this._items;
                }
            },

            /**
             * Background for the legend.
             *
             * @attribute background
             * @type Rect
             */
            background: {}

            /**
             * Properties used to display and style the ChartLegend.  This attribute is inherited from `Renderer`.
             * Below are the default values:
             *
             *  <dl>
             *      <dt>gap</dt><dd>Distance, in pixels, between the `ChartLegend` instance and the chart's content. When `ChartLegend`
             *      is rendered within a `Chart` instance this value is applied.</dd>
             *      <dt>hAlign</dt><dd>Defines the horizontal alignment of the `items` in a `ChartLegend` rendered in a horizontal direction.
             *      This value is applied when the instance's `position` is set to top or bottom. This attribute can be set to left, center
             *      or right. The default value is center.</dd>
             *      <dt>vAlign</dt><dd>Defines the vertical alignment of the `items` in a `ChartLegend` rendered in vertical direction. This
             *      value is applied when the instance's `position` is set to left or right. The attribute can be set to top, middle or
             *      bottom. The default value is middle.</dd>
             *      <dt>item</dt><dd>Set of style properties applied to the `items` of the `ChartLegend`.
             *          <dl>
             *              <dt>hSpacing</dt><dd>Horizontal distance, in pixels, between legend `items`.</dd>
             *              <dt>vSpacing</dt><dd>Vertical distance, in pixels, between legend `items`.</dd>
             *              <dt>label</dt><dd>Properties for the text of an `item`.
             *                  <dl>
             *                      <dt>color</dt><dd>Color of the text. The default values is "#808080".</dd>
             *                      <dt>fontSize</dt><dd>Font size for the text. The default value is "85%".</dd>
             *                  </dl>
             *              </dd>
             *              <dt>marker</dt><dd>Properties for the `item` markers.
             *                  <dl>
             *                      <dt>width</dt><dd>Specifies the width of the markers.</dd>
             *                      <dt>height</dt><dd>Specifies the height of the markers.</dd>
             *                  </dl>
             *              </dd>
             *          </dl>
             *      </dd>
             *      <dt>background</dt><dd>Properties for the `ChartLegend` background.
             *          <dl>
             *              <dt>fill</dt><dd>Properties for the background fill.
             *                  <dl>
             *                      <dt>color</dt><dd>Color for the fill. The default value is "#faf9f2".</dd>
             *                  </dl>
             *              </dd>
             *              <dt>border</dt><dd>Properties for the background border.
             *                  <dl>
             *                      <dt>color</dt><dd>Color for the border. The default value is "#dad8c9".</dd>
             *                      <dt>weight</dt><dd>Weight of the border. The default values is 1.</dd>
             *                  </dl>
             *              </dd>
             *          </dl>
             *      </dd>
             * </dl>
             *
             * @attribute styles
             * @type Object
             */
        }
    });