/** * Adds legend functionality to charts. * * @module charts * @submodule charts-legend */ var TOP = "top", RIGHT = "right", BOTTOM = "bottom", LEFT = "left", EXTERNAL = "external", HORIZONTAL = "horizontal", VERTICAL = "vertical", WIDTH = "width", HEIGHT = "height", POSITION = "position", _X = "x", _Y = "y", PX = "px", PieChartLegend, LEGEND = { setter: function(val) { var legend = this.get("legend"); if(legend) { legend.destroy(true); } if(val instanceof Y.ChartLegend) { legend = val; legend.set("chart", this); } else { val.chart = this; if(!val.hasOwnProperty("render")) { val.render = this.get("contentBox"); val.includeInChartLayout = true; } legend = new Y.ChartLegend(val); } return legend; } }, /** * Contains methods for displaying items horizontally in a legend. * * @module charts * @submodule charts-legend * @class HorizontalLegendLayout */ HorizontalLegendLayout = { /** * Displays items horizontally in a legend. * * @method _positionLegendItems * @param {Array} items Array of items to display in the legend. * @param {Number} maxWidth The width of the largest item in the legend. * @param {Number} maxHeight The height of the largest item in the legend. * @param {Number} totalWidth The total width of all items in a legend. * @param {Number} totalHeight The total height of all items in a legend. * @param {Number} padding The left, top, right and bottom padding properties for the legend. * @param {Number} horizontalGap The horizontal distance between items in a legend. * @param {Number} verticalGap The vertical distance between items in a legend. * @param {String} hAlign The horizontal alignment of the legend. * @protected */ _positionLegendItems: function(items, maxWidth, maxHeight, totalWidth, totalHeight, padding, horizontalGap, verticalGap, hAlign) { var i = 0, rowIterator = 0, item, node, itemWidth, itemHeight, len, width = this.get("width"), rows, rowsLen, row, totalWidthArray, legendWidth, topHeight = padding.top - verticalGap, limit = width - (padding.left + padding.right), left, top, right, bottom; HorizontalLegendLayout._setRowArrays(items, limit, horizontalGap); rows = HorizontalLegendLayout.rowArray; totalWidthArray = HorizontalLegendLayout.totalWidthArray; rowsLen = rows.length; for(; rowIterator < rowsLen; ++ rowIterator) { topHeight += verticalGap; row = rows[rowIterator]; len = row.length; legendWidth = HorizontalLegendLayout.getStartPoint(width, totalWidthArray[rowIterator], hAlign, padding); for(i = 0; i < len; ++i) { item = row[i]; node = item.node; itemWidth = item.width; itemHeight = item.height; item.x = legendWidth; item.y = 0; left = !isNaN(left) ? Math.min(left, legendWidth) : legendWidth; top = !isNaN(top) ? Math.min(top, topHeight) : topHeight; right = !isNaN(right) ? Math.max(legendWidth + itemWidth, right) : legendWidth + itemWidth; bottom = !isNaN(bottom) ? Math.max(topHeight + itemHeight, bottom) : topHeight + itemHeight; node.setStyle("left", legendWidth + PX); node.setStyle("top", topHeight + PX); legendWidth += itemWidth + horizontalGap; } topHeight += item.height; } this._contentRect = { left: left, top: top, right: right, bottom: bottom }; if(this.get("includeInChartLayout")) { this.set("height", topHeight + padding.bottom); } }, /** * Creates row and total width arrays used for displaying multiple rows of * legend items based on the items, available width and horizontalGap for the legend. * * @method _setRowArrays * @param {Array} items Array of legend items to display in a legend. * @param {Number} limit Total available width for displaying items in a legend. * @param {Number} horizontalGap Horizontal distance between items in a legend. * @protected */ _setRowArrays: function(items, limit, horizontalGap) { var item = items[0], rowArray = [[item]], i = 1, rowIterator = 0, len = items.length, totalWidth = item.width, itemWidth, totalWidthArray = [[totalWidth]]; for(; i < len; ++i) { item = items[i]; itemWidth = item.width; if((totalWidth + horizontalGap + itemWidth) <= limit) { totalWidth += horizontalGap + itemWidth; rowArray[rowIterator].push(item); } else { totalWidth = horizontalGap + itemWidth; if(rowArray[rowIterator]) { rowIterator += 1; } rowArray[rowIterator] = [item]; } totalWidthArray[rowIterator] = totalWidth; } HorizontalLegendLayout.rowArray = rowArray; HorizontalLegendLayout.totalWidthArray = totalWidthArray; }, /** * Returns the starting x-coordinate for a row of legend items. * * @method getStartPoint * @param {Number} w Width of the legend. * @param {Number} totalWidth Total width of all labels in the row. * @param {String} align Horizontal alignment of items for the legend. * @param {Object} padding Object contain left, top, right and bottom padding properties. * @return Number * @protected */ getStartPoint: function(w, totalWidth, align, padding) { var startPoint; switch(align) { case LEFT : startPoint = padding.left; break; case "center" : startPoint = (w - totalWidth) * 0.5; break; case RIGHT : startPoint = w - totalWidth - padding.right; break; } return startPoint; } }, /** * Contains methods for displaying items vertically in a legend. * * @module charts * @submodule charts-legend * @class VerticalLegendLayout */ VerticalLegendLayout = { /** * Displays items vertically in a legend. * * @method _positionLegendItems * @param {Array} items Array of items to display in the legend. * @param {Number} maxWidth The width of the largest item in the legend. * @param {Number} maxHeight The height of the largest item in the legend. * @param {Number} totalWidth The total width of all items in a legend. * @param {Number} totalHeight The total height of all items in a legend. * @param {Number} padding The left, top, right and bottom padding properties for the legend. * @param {Number} horizontalGap The horizontal distance between items in a legend. * @param {Number} verticalGap The vertical distance between items in a legend. * @param {String} vAlign The vertical alignment of the legend. * @protected */ _positionLegendItems: function(items, maxWidth, maxHeight, totalWidth, totalHeight, padding, horizontalGap, verticalGap, vAlign) { var i = 0, columnIterator = 0, item, node, itemHeight, itemWidth, len, height = this.get("height"), columns, columnsLen, column, totalHeightArray, legendHeight, leftWidth = padding.left - horizontalGap, legendWidth, limit = height - (padding.top + padding.bottom), left, top, right, bottom; VerticalLegendLayout._setColumnArrays(items, limit, verticalGap); columns = VerticalLegendLayout.columnArray; totalHeightArray = VerticalLegendLayout.totalHeightArray; columnsLen = columns.length; for(; columnIterator < columnsLen; ++ columnIterator) { leftWidth += horizontalGap; column = columns[columnIterator]; len = column.length; legendHeight = VerticalLegendLayout.getStartPoint(height, totalHeightArray[columnIterator], vAlign, padding); legendWidth = 0; for(i = 0; i < len; ++i) { item = column[i]; node = item.node; itemHeight = item.height; itemWidth = item.width; item.y = legendHeight; item.x = leftWidth; left = !isNaN(left) ? Math.min(left, leftWidth) : leftWidth; top = !isNaN(top) ? Math.min(top, legendHeight) : legendHeight; right = !isNaN(right) ? Math.max(leftWidth + itemWidth, right) : leftWidth + itemWidth; bottom = !isNaN(bottom) ? Math.max(legendHeight + itemHeight, bottom) : legendHeight + itemHeight; node.setStyle("left", leftWidth + PX); node.setStyle("top", legendHeight + PX); legendHeight += itemHeight + verticalGap; legendWidth = Math.max(legendWidth, item.width); } leftWidth += legendWidth; } this._contentRect = { left: left, top: top, right: right, bottom: bottom }; if(this.get("includeInChartLayout")) { this.set("width", leftWidth + padding.right); } }, /** * Creates column and total height arrays used for displaying multiple columns of * legend items based on the items, available height and verticalGap for the legend. * * @method _setColumnArrays * @param {Array} items Array of legend items to display in a legend. * @param {Number} limit Total available height for displaying items in a legend. * @param {Number} verticalGap Vertical distance between items in a legend. * @protected */ _setColumnArrays: function(items, limit, verticalGap) { var item = items[0], columnArray = [[item]], i = 1, columnIterator = 0, len = items.length, totalHeight = item.height, itemHeight, totalHeightArray = [[totalHeight]]; for(; i < len; ++i) { item = items[i]; itemHeight = item.height; if((totalHeight + verticalGap + itemHeight) <= limit) { totalHeight += verticalGap + itemHeight; columnArray[columnIterator].push(item); } else { totalHeight = verticalGap + itemHeight; if(columnArray[columnIterator]) { columnIterator += 1; } columnArray[columnIterator] = [item]; } totalHeightArray[columnIterator] = totalHeight; } VerticalLegendLayout.columnArray = columnArray; VerticalLegendLayout.totalHeightArray = totalHeightArray; }, /** * Returns the starting y-coordinate for a column of legend items. * * @method getStartPoint * @param {Number} h Height of the legend. * @param {Number} totalHeight Total height of all labels in the column. * @param {String} align Vertical alignment of items for the legend. * @param {Object} padding Object contain left, top, right and bottom padding properties. * @return Number * @protected */ getStartPoint: function(h, totalHeight, align, padding) { var startPoint; switch(align) { case TOP : startPoint = padding.top; break; case "middle" : startPoint = (h - totalHeight) * 0.5; break; case BOTTOM : startPoint = h - totalHeight - padding.bottom; break; } return startPoint; } }, CartesianChartLegend = Y.Base.create("cartesianChartLegend", Y.CartesianChart, [], { /** * Redraws and position all the components of the chart instance. * * @method _redraw * @private */ _redraw: function() { if(this._drawing) { this._callLater = true; return; } this._drawing = true; this._callLater = false; var w = this.get("width"), h = this.get("height"), layoutBoxDimensions = this._getLayoutBoxDimensions(), leftPaneWidth = layoutBoxDimensions.left, rightPaneWidth = layoutBoxDimensions.right, topPaneHeight = layoutBoxDimensions.top, bottomPaneHeight = layoutBoxDimensions.bottom, leftAxesCollection = this.get("leftAxesCollection"), rightAxesCollection = this.get("rightAxesCollection"), topAxesCollection = this.get("topAxesCollection"), bottomAxesCollection = this.get("bottomAxesCollection"), i = 0, l, axis, graphOverflow = "visible", graph = this.get("graph"), topOverflow, bottomOverflow, leftOverflow, rightOverflow, graphWidth, graphHeight, graphX, graphY, allowContentOverflow = this.get("allowContentOverflow"), diff, rightAxesXCoords, leftAxesXCoords, topAxesYCoords, bottomAxesYCoords, legend = this.get("legend"), graphRect = {}; if(leftAxesCollection) { leftAxesXCoords = []; l = leftAxesCollection.length; for(i = l - 1; i > -1; --i) { leftAxesXCoords.unshift(leftPaneWidth); leftPaneWidth += leftAxesCollection[i].get("width"); } } if(rightAxesCollection) { rightAxesXCoords = []; l = rightAxesCollection.length; i = 0; for(i = l - 1; i > -1; --i) { rightPaneWidth += rightAxesCollection[i].get("width"); rightAxesXCoords.unshift(w - rightPaneWidth); } } if(topAxesCollection) { topAxesYCoords = []; l = topAxesCollection.length; for(i = l - 1; i > -1; --i) { topAxesYCoords.unshift(topPaneHeight); topPaneHeight += topAxesCollection[i].get("height"); } } if(bottomAxesCollection) { bottomAxesYCoords = []; l = bottomAxesCollection.length; for(i = l - 1; i > -1; --i) { bottomPaneHeight += bottomAxesCollection[i].get("height"); bottomAxesYCoords.unshift(h - bottomPaneHeight); } } graphWidth = w - (leftPaneWidth + rightPaneWidth); graphHeight = h - (bottomPaneHeight + topPaneHeight); graphRect.left = leftPaneWidth; graphRect.top = topPaneHeight; graphRect.bottom = h - bottomPaneHeight; graphRect.right = w - rightPaneWidth; if(!allowContentOverflow) { topOverflow = this._getTopOverflow(leftAxesCollection, rightAxesCollection); bottomOverflow = this._getBottomOverflow(leftAxesCollection, rightAxesCollection); leftOverflow = this._getLeftOverflow(bottomAxesCollection, topAxesCollection); rightOverflow = this._getRightOverflow(bottomAxesCollection, topAxesCollection); diff = topOverflow - topPaneHeight; if(diff > 0) { graphRect.top = topOverflow; if(topAxesYCoords) { i = 0; l = topAxesYCoords.length; for(; i < l; ++i) { topAxesYCoords[i] += diff; } } } diff = bottomOverflow - bottomPaneHeight; if(diff > 0) { graphRect.bottom = h - bottomOverflow; if(bottomAxesYCoords) { i = 0; l = bottomAxesYCoords.length; for(; i < l; ++i) { bottomAxesYCoords[i] -= diff; } } } diff = leftOverflow - leftPaneWidth; if(diff > 0) { graphRect.left = leftOverflow; if(leftAxesXCoords) { i = 0; l = leftAxesXCoords.length; for(; i < l; ++i) { leftAxesXCoords[i] += diff; } } } diff = rightOverflow - rightPaneWidth; if(diff > 0) { graphRect.right = w - rightOverflow; if(rightAxesXCoords) { i = 0; l = rightAxesXCoords.length; for(; i < l; ++i) { rightAxesXCoords[i] -= diff; } } } } graphWidth = graphRect.right - graphRect.left; graphHeight = graphRect.bottom - graphRect.top; graphX = graphRect.left; graphY = graphRect.top; if(legend) { if(legend.get("includeInChartLayout")) { switch(legend.get("position")) { case "left" : legend.set("y", graphY); legend.set("height", graphHeight); break; case "top" : legend.set("x", graphX); legend.set("width", graphWidth); break; case "bottom" : legend.set("x", graphX); legend.set("width", graphWidth); break; case "right" : legend.set("y", graphY); legend.set("height", graphHeight); break; } } } if(topAxesCollection) { l = topAxesCollection.length; i = 0; for(; i < l; i++) { axis = topAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + PX); axis.get("boundingBox").setStyle("top", topAxesYCoords[i] + PX); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(bottomAxesCollection) { l = bottomAxesCollection.length; i = 0; for(; i < l; i++) { axis = bottomAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + PX); axis.get("boundingBox").setStyle("top", bottomAxesYCoords[i] + PX); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(leftAxesCollection) { l = leftAxesCollection.length; i = 0; for(; i < l; ++i) { axis = leftAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + PX); axis.get("boundingBox").setStyle("left", leftAxesXCoords[i] + PX); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(rightAxesCollection) { l = rightAxesCollection.length; i = 0; for(; i < l; ++i) { axis = rightAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + PX); axis.get("boundingBox").setStyle("left", rightAxesXCoords[i] + PX); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } this._drawing = false; if(this._callLater) { this._redraw(); return; } if(graph) { graph.get("boundingBox").setStyle("left", graphX + PX); graph.get("boundingBox").setStyle("top", graphY + PX); graph.set("width", graphWidth); graph.set("height", graphHeight); graph.get("boundingBox").setStyle("overflow", graphOverflow); } if(this._overlay) { this._overlay.setStyle("left", graphX + PX); this._overlay.setStyle("top", graphY + PX); this._overlay.setStyle("width", graphWidth + PX); this._overlay.setStyle("height", graphHeight + PX); } }, /** * Positions the legend in a chart and returns the properties of the legend to be used in the * chart's layout algorithm. * * @method _getLayoutDimensions * @return {Object} The left, top, right and bottom values for the legend. * @protected */ _getLayoutBoxDimensions: function() { var box = { top: 0, right: 0, bottom: 0, left: 0 }, legend = this.get("legend"), position, direction, dimension, size, w = this.get(WIDTH), h = this.get(HEIGHT), gap; if(legend && legend.get("includeInChartLayout")) { gap = legend.get("styles").gap; position = legend.get(POSITION); if(position !== EXTERNAL) { direction = legend.get("direction"); dimension = direction === HORIZONTAL ? HEIGHT : WIDTH; size = legend.get(dimension); box[position] = size + gap; switch(position) { case TOP : legend.set(_Y, 0); break; case BOTTOM : legend.set(_Y, h - size); break; case RIGHT : legend.set(_X, w - size); break; case LEFT: legend.set(_X, 0); break; } } } return box; }, /** * Destructor implementation for the CartesianChart class. Calls destroy on all axes, series, legend (if available) and the Graph instance. * Removes the tooltip and overlay HTML elements. * * @method destructor * @protected */ destructor: function() { var legend = this.get("legend"); if(legend) { legend.destroy(true); } } }, { ATTRS: { legend: LEGEND } }); Y.CartesianChart = CartesianChartLegend;