Version 3.18.1
Show:

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} labelStyles 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 {String} text Text for the legendItem
                 * @return Object
                 * @private
                 */
                _getLegendItem: function(node, shapeClass, fill, border, labelStyles, w, h, text)
                {
                    var containerNode = Y.Node.create("<div>"),
                        textField = Y.Node.create("<span>"),
                        shape,
                        dimension,
                        padding,
                        left,
                        item,
                        ShapeClass = shapeClass;
                    containerNode.setStyle(POSITION, "absolute");
                    textField.setStyle(POSITION, "absolute");
                    textField.setStyles(labelStyles);
                    textField.set("text", text);
                    containerNode.appendChild(textField);
                    node.append(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
                     */
                }
            });