API Docs for: 3.10.3
Show:

File: charts/js/ChartLegend.js

  1. /**
  2. * ChartLegend provides a legend for a chart.
  3. *
  4. * @class ChartLegend
  5. * @module charts
  6. * @submodule charts-legend
  7. * @extends Widget
  8. */
  9. Y.ChartLegend = Y.Base.create("chartlegend", Y.Widget, [Y.Renderer], {
  10. /**
  11. * Initializes the chart.
  12. *
  13. * @method initializer
  14. * @private
  15. */
  16. initializer: function()
  17. {
  18. this._items = [];
  19. },
  20.  
  21. /**
  22. * @method renderUI
  23. * @private
  24. */
  25. renderUI: function()
  26. {
  27. var bb = this.get("boundingBox"),
  28. cb = this.get("contentBox"),
  29. styles = this.get("styles").background,
  30. background = new Y.Rect({
  31. graphic: cb,
  32. fill: styles.fill,
  33. stroke: styles.border
  34. });
  35. bb.setStyle("display", "block");
  36. bb.setStyle("position", "absolute");
  37. this.set("background", background);
  38. },
  39.  
  40. /**
  41. * @method bindUI
  42. * @private
  43. */
  44. bindUI: function()
  45. {
  46. this.get("chart").after("seriesCollectionChange", Y.bind(this._updateHandler, this));
  47. this.get("chart").after("stylesChange", Y.bind(this._updateHandler, this));
  48. this.after("stylesChange", this._updateHandler);
  49. this.after("positionChange", this._positionChangeHandler);
  50. this.after("widthChange", this._handleSizeChange);
  51. this.after("heightChange", this._handleSizeChange);
  52. },
  53.  
  54. /**
  55. * @method syncUI
  56. * @private
  57. */
  58. syncUI: function()
  59. {
  60. var w = this.get("width"),
  61. h = this.get("height");
  62. if(isFinite(w) && isFinite(h) && w > 0 && h > 0)
  63. {
  64. this._drawLegend();
  65. }
  66. },
  67.  
  68. /**
  69. * Handles changes to legend.
  70. *
  71. * @method _updateHandler
  72. * @param {Object} e Event object
  73. * @private
  74. */
  75. _updateHandler: function()
  76. {
  77. if(this.get("rendered"))
  78. {
  79. this._drawLegend();
  80. }
  81. },
  82.  
  83. /**
  84. * Handles position changes.
  85. *
  86. * @method _positionChangeHandler
  87. * @param {Object} e Event object
  88. * @private
  89. */
  90. _positionChangeHandler: function()
  91. {
  92. var chart = this.get("chart"),
  93. parentNode = this._parentNode;
  94. if(parentNode && ((chart && this.get("includeInChartLayout"))))
  95. {
  96. this.fire("legendRendered");
  97. }
  98. else if(this.get("rendered"))
  99. {
  100. this._drawLegend();
  101. }
  102. },
  103.  
  104. /**
  105. * Updates the legend when the size changes.
  106. *
  107. * @method _handleSizeChange
  108. * @param {Object} e Event object.
  109. * @private
  110. */
  111. _handleSizeChange: function(e)
  112. {
  113. var attrName = e.attrName,
  114. pos = this.get(POSITION),
  115. vert = pos === LEFT || pos === RIGHT,
  116. hor = pos === BOTTOM || pos === TOP;
  117. if((hor && attrName === WIDTH) || (vert && attrName === HEIGHT))
  118. {
  119. this._drawLegend();
  120. }
  121. },
  122.  
  123. /**
  124. * Draws the legend
  125. *
  126. * @method _drawLegend
  127. * @private
  128. */
  129. _drawLegend: function()
  130. {
  131. if(this._drawing)
  132. {
  133. this._callLater = true;
  134. return;
  135. }
  136. this._drawing = true;
  137. this._callLater = false;
  138. if(this.get("includeInChartLayout"))
  139. {
  140. this.get("chart")._itemRenderQueue.unshift(this);
  141. }
  142. var chart = this.get("chart"),
  143. node = this.get("contentBox"),
  144. seriesCollection = chart.get("seriesCollection"),
  145. series,
  146. styles = this.get("styles"),
  147. padding = styles.padding,
  148. itemStyles = styles.item,
  149. seriesStyles,
  150. hSpacing = itemStyles.hSpacing,
  151. vSpacing = itemStyles.vSpacing,
  152. direction = this.get("direction"),
  153. align = direction === "vertical" ? styles.vAlign : styles.hAlign,
  154. marker = styles.marker,
  155. labelStyles = itemStyles.label,
  156. displayName,
  157. layout = this._layout[direction],
  158. i,
  159. len,
  160. isArray,
  161. legendShape,
  162. shape,
  163. shapeClass,
  164. item,
  165. fill,
  166. border,
  167. fillColors,
  168. borderColors,
  169. borderWeight,
  170. items = [],
  171. markerWidth = marker.width,
  172. markerHeight = marker.height,
  173. totalWidth = 0 - hSpacing,
  174. totalHeight = 0 - vSpacing,
  175. maxWidth = 0,
  176. maxHeight = 0,
  177. itemWidth,
  178. itemHeight;
  179. if(marker && marker.shape)
  180. {
  181. legendShape = marker.shape;
  182. }
  183. this._destroyLegendItems();
  184. if(chart instanceof Y.PieChart)
  185. {
  186. series = seriesCollection[0];
  187. displayName = series.get("categoryAxis").getDataByKey(series.get("categoryKey"));
  188. seriesStyles = series.get("styles").marker;
  189. fillColors = seriesStyles.fill.colors;
  190. borderColors = seriesStyles.border.colors;
  191. borderWeight = seriesStyles.border.weight;
  192. i = 0;
  193. len = displayName.length;
  194. shape = legendShape || Y.Circle;
  195. isArray = Y.Lang.isArray(shape);
  196. for(; i < len; ++i)
  197. {
  198. shape = isArray ? shape[i] : shape;
  199. fill = {
  200. color: fillColors[i]
  201. };
  202. border = {
  203. colors: borderColors[i],
  204. weight: borderWeight
  205. };
  206. displayName = chart.getSeriesItems(series, i).category.value;
  207. item = this._getLegendItem(node, this._getShapeClass(shape), fill, border, labelStyles, markerWidth, markerHeight, displayName);
  208. itemWidth = item.width;
  209. itemHeight = item.height;
  210. maxWidth = Math.max(maxWidth, itemWidth);
  211. maxHeight = Math.max(maxHeight, itemHeight);
  212. totalWidth += itemWidth + hSpacing;
  213. totalHeight += itemHeight + vSpacing;
  214. items.push(item);
  215. }
  216. }
  217. else
  218. {
  219. i = 0;
  220. len = seriesCollection.length;
  221. for(; i < len; ++i)
  222. {
  223. series = seriesCollection[i];
  224. seriesStyles = this._getStylesBySeriesType(series, shape);
  225. if(!legendShape)
  226. {
  227. shape = seriesStyles.shape;
  228. if(!shape)
  229. {
  230. shape = Y.Circle;
  231. }
  232. }
  233. shapeClass = Y.Lang.isArray(shape) ? shape[i] : shape;
  234. item = this._getLegendItem(
  235. node,
  236. this._getShapeClass(shape),
  237. seriesStyles.fill,
  238. seriesStyles.border,
  239. labelStyles,
  240. markerWidth,
  241. markerHeight,
  242. series.get("valueDisplayName")
  243. );
  244. itemWidth = item.width;
  245. itemHeight = item.height;
  246. maxWidth = Math.max(maxWidth, itemWidth);
  247. maxHeight = Math.max(maxHeight, itemHeight);
  248. totalWidth += itemWidth + hSpacing;
  249. totalHeight += itemHeight + vSpacing;
  250. items.push(item);
  251. }
  252. }
  253. this._drawing = false;
  254. if(this._callLater)
  255. {
  256. this._drawLegend();
  257. }
  258. else
  259. {
  260. layout._positionLegendItems.apply(
  261. this,
  262. [items, maxWidth, maxHeight, totalWidth, totalHeight, padding, hSpacing, vSpacing, align]
  263. );
  264. this._updateBackground(styles);
  265. this.fire("legendRendered");
  266. }
  267. },
  268.  
  269. /**
  270. * Updates the background for the legend.
  271. *
  272. * @method _updateBackground
  273. * @param {Object} styles Reference to the legend's styles attribute
  274. * @private
  275. */
  276. _updateBackground: function(styles)
  277. {
  278. var backgroundStyles = styles.background,
  279. contentRect = this._contentRect,
  280. padding = styles.padding,
  281. x = contentRect.left - padding.left,
  282. y = contentRect.top - padding.top,
  283. w = contentRect.right - x + padding.right,
  284. h = contentRect.bottom - y + padding.bottom;
  285. this.get("background").set({
  286. fill: backgroundStyles.fill,
  287. stroke: backgroundStyles.border,
  288. width: w,
  289. height: h,
  290. x: x,
  291. y: y
  292. });
  293. },
  294.  
  295. /**
  296. * Retrieves the marker styles based on the type of series. For series that contain a marker, the marker styles are returned.
  297. *
  298. * @method _getStylesBySeriesType
  299. * @param {CartesianSeries | PieSeries} The series in which the style properties will be received.
  300. * @return Object An object containing fill, border and shape information.
  301. * @private
  302. */
  303. _getStylesBySeriesType: function(series)
  304. {
  305. var styles = series.get("styles"),
  306. color;
  307. if(series instanceof Y.LineSeries || series instanceof Y.StackedLineSeries)
  308. {
  309. styles = series.get("styles").line;
  310. color = styles.color || series._getDefaultColor(series.get("graphOrder"), "line");
  311. return {
  312. border: {
  313. weight: 1,
  314. color: color
  315. },
  316. fill: {
  317. color: color
  318. }
  319. };
  320. }
  321. else if(series instanceof Y.AreaSeries || series instanceof Y.StackedAreaSeries)
  322. {
  323. styles = series.get("styles").area;
  324. color = styles.color || series._getDefaultColor(series.get("graphOrder"), "slice");
  325. return {
  326. border: {
  327. weight: 1,
  328. color: color
  329. },
  330. fill: {
  331. color: color
  332. }
  333. };
  334. }
  335. else
  336. {
  337. styles = series.get("styles").marker;
  338. return {
  339. fill: styles.fill,
  340.  
  341. border: {
  342. weight: styles.border.weight,
  343.  
  344. color: styles.border.color,
  345.  
  346. shape: styles.shape
  347. },
  348. shape: styles.shape
  349. };
  350. }
  351. },
  352.  
  353. /**
  354. * Returns a legend item consisting of the following properties:
  355. * <dl>
  356. * <dt>node</dt><dd>The `Node` containing the legend item elements.</dd>
  357. * <dt>shape</dt><dd>The `Shape` element for the legend item.</dd>
  358. * <dt>textNode</dt><dd>The `Node` containing the text></dd>
  359. * <dt>text</dt><dd></dd>
  360. * </dl>
  361. *
  362. * @method _getLegendItem
  363. * @param {Node} shapeProps Reference to the `node` attribute.
  364. * @param {String | Class} shapeClass The type of shape
  365. * @param {Object} fill Properties for the shape's fill
  366. * @param {Object} border Properties for the shape's border
  367. * @param {String} text String to be rendered as the legend's text
  368. * @param {Number} width Total width of the legend item
  369. * @param {Number} height Total height of the legend item
  370. * @param {HTML | String} text Text for the legendItem
  371. * @return Object
  372. * @private
  373. */
  374. _getLegendItem: function(node, shapeClass, fill, border, labelStyles, w, h, text)
  375. {
  376. var containerNode = Y.one(DOCUMENT.createElement("div")),
  377. textField = Y.one(DOCUMENT.createElement("span")),
  378. shape,
  379. dimension,
  380. padding,
  381. left,
  382. item,
  383. ShapeClass = shapeClass;
  384. containerNode.setStyle(POSITION, "absolute");
  385. textField.setStyle(POSITION, "absolute");
  386. textField.setStyles(labelStyles);
  387. textField.appendChild(DOCUMENT.createTextNode(text));
  388. containerNode.appendChild(textField);
  389. node.appendChild(containerNode);
  390. dimension = textField.get("offsetHeight");
  391. padding = dimension - h;
  392. left = w + padding + 2;
  393. textField.setStyle("left", left + PX);
  394. containerNode.setStyle("height", dimension + PX);
  395. containerNode.setStyle("width", (left + textField.get("offsetWidth")) + PX);
  396. shape = new ShapeClass({
  397. fill: fill,
  398. stroke: border,
  399. width: w,
  400. height: h,
  401. x: padding * 0.5,
  402. y: padding * 0.5,
  403. w: w,
  404. h: h,
  405. graphic: containerNode
  406. });
  407. textField.setStyle("left", dimension + PX);
  408. item = {
  409. node: containerNode,
  410. width: containerNode.get("offsetWidth"),
  411. height: containerNode.get("offsetHeight"),
  412. shape: shape,
  413. textNode: textField,
  414. text: text
  415. };
  416. this._items.push(item);
  417. return item;
  418. },
  419.  
  420. /**
  421. * Evaluates and returns correct class for drawing a shape.
  422. *
  423. * @method _getShapeClass
  424. * @return Shape
  425. * @private
  426. */
  427. _getShapeClass: function()
  428. {
  429. var graphic = this.get("background").get("graphic");
  430. return graphic._getShapeClass.apply(graphic, arguments);
  431. },
  432.  
  433. /**
  434. * Returns the default hash for the `styles` attribute.
  435. *
  436. * @method _getDefaultStyles
  437. * @return Object
  438. * @protected
  439. */
  440. _getDefaultStyles: function()
  441. {
  442. var styles = {
  443. padding: {
  444. top: 8,
  445. right: 8,
  446. bottom: 8,
  447. left: 9
  448. },
  449. gap: 10,
  450. hAlign: "center",
  451. vAlign: "top",
  452. marker: this._getPlotDefaults(),
  453. item: {
  454. hSpacing: 10,
  455. vSpacing: 5,
  456. label: {
  457. color:"#808080",
  458. fontSize:"85%",
  459. whiteSpace: "nowrap"
  460. }
  461. },
  462. background: {
  463. shape: "rect",
  464. fill:{
  465. color:"#faf9f2"
  466. },
  467. border: {
  468. color:"#dad8c9",
  469. weight: 1
  470. }
  471. }
  472. };
  473. return styles;
  474. },
  475.  
  476. /**
  477. * Gets the default values for series that use the utility. This method is used by
  478. * the class' `styles` attribute's getter to get build default values.
  479. *
  480. * @method _getPlotDefaults
  481. * @return Object
  482. * @protected
  483. */
  484. _getPlotDefaults: function()
  485. {
  486. var defs = {
  487. width: 10,
  488. height: 10
  489. };
  490. return defs;
  491. },
  492.  
  493. /**
  494. * Destroys legend items.
  495. *
  496. * @method _destroyLegendItems
  497. * @private
  498. */
  499. _destroyLegendItems: function()
  500. {
  501. var item;
  502. if(this._items)
  503. {
  504. while(this._items.length > 0)
  505. {
  506. item = this._items.shift();
  507. item.shape.get("graphic").destroy();
  508. item.node.empty();
  509. item.node.destroy(true);
  510. item.node = null;
  511. item = null;
  512. }
  513. }
  514. this._items = [];
  515. },
  516.  
  517. /**
  518. * Maps layout classes.
  519. *
  520. * @property _layout
  521. * @private
  522. */
  523. _layout: {
  524. vertical: VerticalLegendLayout,
  525. horizontal: HorizontalLegendLayout
  526. },
  527.  
  528. /**
  529. * Destructor implementation ChartLegend class. Removes all items and the Graphic instance from the widget.
  530. *
  531. * @method destructor
  532. * @protected
  533. */
  534. destructor: function()
  535. {
  536. var background = this.get("background"),
  537. backgroundGraphic;
  538. this._destroyLegendItems();
  539. if(background)
  540. {
  541. backgroundGraphic = background.get("graphic");
  542. if(backgroundGraphic)
  543. {
  544. backgroundGraphic.destroy();
  545. }
  546. else
  547. {
  548. background.destroy();
  549. }
  550. }
  551.  
  552. }
  553. }, {
  554. ATTRS: {
  555. /**
  556. * Indicates whether the chart's contentBox is the parentNode for the legend.
  557. *
  558. * @attribute includeInChartLayout
  559. * @type Boolean
  560. * @private
  561. */
  562. includeInChartLayout: {
  563. value: false
  564. },
  565.  
  566. /**
  567. * Reference to the `Chart` instance.
  568. *
  569. * @attribute chart
  570. * @type Chart
  571. */
  572. chart: {
  573. setter: function(val)
  574. {
  575. this.after("legendRendered", Y.bind(val._itemRendered, val));
  576. return val;
  577. }
  578. },
  579.  
  580. /**
  581. * Indicates the direction in relation of the legend's layout. The `direction` of the legend is determined by its
  582. * `position` value.
  583. *
  584. * @attribute direction
  585. * @type String
  586. */
  587. direction: {
  588. value: "vertical"
  589. },
  590.  
  591. /**
  592. * Indicates the position and direction of the legend. Possible values are `left`, `top`, `right` and `bottom`.
  593. * Values of `left` and `right` values have a `direction` of `vertical`. Values of `top` and `bottom` values have
  594. * a `direction` of `horizontal`.
  595. *
  596. * @attribute position
  597. * @type String
  598. */
  599. position: {
  600. lazyAdd: false,
  601.  
  602. value: "right",
  603.  
  604. setter: function(val)
  605. {
  606. if(val === TOP || val === BOTTOM)
  607. {
  608. this.set("direction", HORIZONTAL);
  609. }
  610. else if(val === LEFT || val === RIGHT)
  611. {
  612. this.set("direction", VERTICAL);
  613. }
  614. return val;
  615. }
  616. },
  617.  
  618. /**
  619. * The width of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
  620. * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
  621. * `width` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
  622. * determined by the direction of the legend. If the `position` is `left` or `right` or the `direction` is
  623. * `vertical`, width is `readOnly`. If the position is `top` or `bottom` or the `direction` is `horizontal`,
  624. * width can be explicitly set. If width is not explicitly set, the width will be determined by the width of the
  625. * legend's parent element.
  626. *
  627. * @attribute width
  628. * @type Number
  629. */
  630. width: {
  631. getter: function()
  632. {
  633. var chart = this.get("chart"),
  634. parentNode = this._parentNode;
  635. if(parentNode)
  636. {
  637. if((chart && this.get("includeInChartLayout")) || this._width)
  638. {
  639. if(!this._width)
  640. {
  641. this._width = 0;
  642. }
  643. return this._width;
  644. }
  645. else
  646. {
  647. return parentNode.get("offsetWidth");
  648. }
  649. }
  650. return "";
  651. },
  652.  
  653. setter: function(val)
  654. {
  655. this._width = val;
  656. return val;
  657. }
  658. },
  659.  
  660. /**
  661. * The height of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
  662. * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
  663. * `height` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
  664. * determined by the direction of the legend. If the `position` is `top` or `bottom` or the `direction` is
  665. * `horizontal`, height is `readOnly`. If the position is `left` or `right` or the `direction` is `vertical`,
  666. * height can be explicitly set. If height is not explicitly set, the height will be determined by the width of the
  667. * legend's parent element.
  668. *
  669. * @attribute height
  670. * @type Number
  671. */
  672. height: {
  673. valueFn: "_heightGetter",
  674.  
  675. getter: function()
  676. {
  677. var chart = this.get("chart"),
  678. parentNode = this._parentNode;
  679. if(parentNode)
  680. {
  681. if((chart && this.get("includeInChartLayout")) || this._height)
  682. {
  683. if(!this._height)
  684. {
  685. this._height = 0;
  686. }
  687. return this._height;
  688. }
  689. else
  690. {
  691. return parentNode.get("offsetHeight");
  692. }
  693. }
  694. return "";
  695. },
  696.  
  697. setter: function(val)
  698. {
  699. this._height = val;
  700. return val;
  701. }
  702. },
  703.  
  704. /**
  705. * Indicates the x position of legend.
  706. *
  707. * @attribute x
  708. * @type Number
  709. * @readOnly
  710. */
  711. x: {
  712. lazyAdd: false,
  713.  
  714. value: 0,
  715.  
  716. setter: function(val)
  717. {
  718. var node = this.get("boundingBox");
  719. if(node)
  720. {
  721. node.setStyle(LEFT, val + PX);
  722. }
  723. return val;
  724. }
  725. },
  726.  
  727. /**
  728. * Indicates the y position of legend.
  729. *
  730. * @attribute y
  731. * @type Number
  732. * @readOnly
  733. */
  734. y: {
  735. lazyAdd: false,
  736.  
  737. value: 0,
  738.  
  739. setter: function(val)
  740. {
  741. var node = this.get("boundingBox");
  742. if(node)
  743. {
  744. node.setStyle(TOP, val + PX);
  745. }
  746. return val;
  747. }
  748. },
  749.  
  750. /**
  751. * Array of items contained in the legend. Each item is an object containing the following properties:
  752. *
  753. * <dl>
  754. * <dt>node</dt><dd>Node containing text for the legend item.</dd>
  755. * <dt>marker</dt><dd>Shape for the legend item.</dd>
  756. * </dl>
  757. *
  758. * @attribute items
  759. * @type Array
  760. * @readOnly
  761. */
  762. items: {
  763. getter: function()
  764. {
  765. return this._items;
  766. }
  767. },
  768.  
  769. /**
  770. * Background for the legend.
  771. *
  772. * @attribute background
  773. * @type Rect
  774. */
  775. background: {}
  776.  
  777. /**
  778. * Properties used to display and style the ChartLegend. This attribute is inherited from `Renderer`.
  779. * Below are the default values:
  780. *
  781. * <dl>
  782. * <dt>gap</dt><dd>Distance, in pixels, between the `ChartLegend` instance and the chart's content. When `ChartLegend`
  783. * is rendered within a `Chart` instance this value is applied.</dd>
  784. * <dt>hAlign</dt><dd>Defines the horizontal alignment of the `items` in a `ChartLegend` rendered in a horizontal direction.
  785. * This value is applied when the instance's `position` is set to top or bottom. This attribute can be set to left, center
  786. * or right. The default value is center.</dd>
  787. * <dt>vAlign</dt><dd>Defines the vertical alignment of the `items` in a `ChartLegend` rendered in vertical direction. This
  788. * value is applied when the instance's `position` is set to left or right. The attribute can be set to top, middle or
  789. * bottom. The default value is middle.</dd>
  790. * <dt>item</dt><dd>Set of style properties applied to the `items` of the `ChartLegend`.
  791. * <dl>
  792. * <dt>hSpacing</dt><dd>Horizontal distance, in pixels, between legend `items`.</dd>
  793. * <dt>vSpacing</dt><dd>Vertical distance, in pixels, between legend `items`.</dd>
  794. * <dt>label</dt><dd>Properties for the text of an `item`.
  795. * <dl>
  796. * <dt>color</dt><dd>Color of the text. The default values is "#808080".</dd>
  797. * <dt>fontSize</dt><dd>Font size for the text. The default value is "85%".</dd>
  798. * </dl>
  799. * </dd>
  800. * <dt>marker</dt><dd>Properties for the `item` markers.
  801. * <dl>
  802. * <dt>width</dt><dd>Specifies the width of the markers.</dd>
  803. * <dt>height</dt><dd>Specifies the height of the markers.</dd>
  804. * </dl>
  805. * </dd>
  806. * </dl>
  807. * </dd>
  808. * <dt>background</dt><dd>Properties for the `ChartLegend` background.
  809. * <dl>
  810. * <dt>fill</dt><dd>Properties for the background fill.
  811. * <dl>
  812. * <dt>color</dt><dd>Color for the fill. The default value is "#faf9f2".</dd>
  813. * </dl>
  814. * </dd>
  815. * <dt>border</dt><dd>Properties for the background border.
  816. * <dl>
  817. * <dt>color</dt><dd>Color for the border. The default value is "#dad8c9".</dd>
  818. * <dt>weight</dt><dd>Weight of the border. The default values is 1.</dd>
  819. * </dl>
  820. * </dd>
  821. * </dl>
  822. * </dd>
  823. * </dl>
  824. *
  825. * @attribute styles
  826. * @type Object
  827. */
  828. }
  829. });
  830.