API Docs for: 3.17.2
Show:

File: charts/js/PieSeries.js

  1. /**
  2. * Provides functionality for creating a pie series.
  3. *
  4. * @module charts
  5. * @submodule series-pie
  6. */
  7. /**
  8. * PieSeries visualizes data as a circular chart divided into wedges which represent data as a
  9. * percentage of a whole.
  10. *
  11. * @class PieSeries
  12. * @constructor
  13. * @extends SeriesBase
  14. * @uses Plots
  15. * @param {Object} config (optional) Configuration parameters.
  16. * @submodule series-pie
  17. */
  18. var CONFIG = Y.config,
  19. DOCUMENT = CONFIG.doc,
  20. _getClassName = Y.ClassNameManager.getClassName,
  21. SERIES_MARKER = _getClassName("seriesmarker");
  22. Y.PieSeries = Y.Base.create("pieSeries", Y.SeriesBase, [Y.Plots], {
  23. /**
  24. * Image map used for interactivity when rendered with canvas.
  25. *
  26. * @property _map
  27. * @type HTMLElement
  28. * @private
  29. */
  30. _map: null,
  31.  
  32. /**
  33. * Image used for image map when rendered with canvas.
  34. *
  35. * @property _image
  36. * @type HTMLElement
  37. * @private
  38. */
  39. _image: null,
  40.  
  41. /**
  42. * Creates or updates the image map when rendered with canvas.
  43. *
  44. * @method _setMap
  45. * @private
  46. */
  47. _setMap: function()
  48. {
  49. var id = "pieHotSpotMapi_" + Math.round(100000 * Math.random()),
  50. graph = this.get("graph"),
  51. graphic,
  52. cb,
  53. areaNode;
  54. if(graph)
  55. {
  56. cb = graph.get("contentBox");
  57. }
  58. else
  59. {
  60. graphic = this.get("graphic");
  61. cb = graphic.get("node");
  62. }
  63. if(this._image)
  64. {
  65. cb.removeChild(this._image);
  66. while(this._areaNodes && this._areaNodes.length > 0)
  67. {
  68. areaNode = this._areaNodes.shift();
  69. this._map.removeChild(areaNode);
  70. }
  71. cb.removeChild(this._map);
  72. }
  73. this._image = DOCUMENT.createElement("img");
  74. this._image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSB" +
  75. "JbWFnZVJlYWR5ccllPAAAABJJREFUeNpiZGBgSGPAAgACDAAIkABoFyloZQAAAABJRU5ErkJggg==";
  76. cb.appendChild(this._image);
  77. this._image.style.position = "absolute";
  78. this._image.style.left = "0px";
  79. this._image.style.top = "0px";
  80. this._image.setAttribute("usemap", "#" + id);
  81. this._image.style.zIndex = 3;
  82. this._image.style.opacity = 0;
  83. this._image.setAttribute("alt", "imagemap");
  84. this._map = DOCUMENT.createElement("map");
  85. cb.appendChild(this._map);
  86. this._map.setAttribute("name", id);
  87. this._map.setAttribute("id", id);
  88. this._areaNodes = [];
  89. },
  90.  
  91. /**
  92. * Storage for `categoryDisplayName` attribute.
  93. *
  94. * @property _categoryDisplayName
  95. * @private
  96. */
  97. _categoryDisplayName: null,
  98.  
  99. /**
  100. * Storage for `valueDisplayName` attribute.
  101. *
  102. * @property _valueDisplayName
  103. * @private
  104. */
  105. _valueDisplayName: null,
  106.  
  107. /**
  108. * Adds event listeners.
  109. *
  110. * @method addListeners
  111. * @private
  112. */
  113. addListeners: function()
  114. {
  115. var categoryAxis = this.get("categoryAxis"),
  116. valueAxis = this.get("valueAxis");
  117. if(categoryAxis)
  118. {
  119. categoryAxis.after("dataReady", Y.bind(this._categoryDataChangeHandler, this));
  120. categoryAxis.after("dataUpdate", Y.bind(this._categoryDataChangeHandler, this));
  121. }
  122. if(valueAxis)
  123. {
  124. valueAxis.after("dataReady", Y.bind(this._valueDataChangeHandler, this));
  125. valueAxis.after("dataUpdate", Y.bind(this._valueDataChangeHandler, this));
  126. }
  127. this.after("categoryAxisChange", this.categoryAxisChangeHandler);
  128. this.after("valueAxisChange", this.valueAxisChangeHandler);
  129. this._stylesChangeHandle = this.after("stylesChange", this._updateHandler);
  130. this._visibleChangeHandle = this.after("visibleChange", this._handleVisibleChange);
  131. },
  132.  
  133. /**
  134. * Draws the series.
  135. *
  136. * @method validate
  137. * @private
  138. */
  139. validate: function()
  140. {
  141. this.draw();
  142. this._renderered = true;
  143. },
  144.  
  145. /**
  146. * Event handler for the categoryAxisChange event.
  147. *
  148. * @method _categoryAxisChangeHandler
  149. * @param {Object} e Event object.
  150. * @private
  151. */
  152. _categoryAxisChangeHandler: function()
  153. {
  154. var categoryAxis = this.get("categoryAxis");
  155. categoryAxis.after("dataReady", Y.bind(this._categoryDataChangeHandler, this));
  156. categoryAxis.after("dataUpdate", Y.bind(this._categoryDataChangeHandler, this));
  157. },
  158.  
  159. /**
  160. * Event handler for the valueAxisChange event.
  161. *
  162. * @method _valueAxisChangeHandler
  163. * @param {Object} e Event object.
  164. * @private
  165. */
  166. _valueAxisChangeHandler: function()
  167. {
  168. var valueAxis = this.get("valueAxis");
  169. valueAxis.after("dataReady", Y.bind(this._valueDataChangeHandler, this));
  170. valueAxis.after("dataUpdate", Y.bind(this._valueDataChangeHandler, this));
  171. },
  172.  
  173. /**
  174. * Constant used to generate unique id.
  175. *
  176. * @property GUID
  177. * @type String
  178. * @private
  179. */
  180. GUID: "pieseries",
  181.  
  182. /**
  183. * Event handler for categoryDataChange event.
  184. *
  185. * @method _categoryDataChangeHandler
  186. * @param {Object} event Event object.
  187. * @private
  188. */
  189. _categoryDataChangeHandler: function()
  190. {
  191. if(this._rendered && this.get("categoryKey") && this.get("valueKey"))
  192. {
  193. this.draw();
  194. }
  195. },
  196.  
  197. /**
  198. * Event handler for valueDataChange event.
  199. *
  200. * @method _valueDataChangeHandler
  201. * @param {Object} event Event object.
  202. * @private
  203. */
  204. _valueDataChangeHandler: function()
  205. {
  206. if(this._rendered && this.get("categoryKey") && this.get("valueKey"))
  207. {
  208. this.draw();
  209. }
  210. },
  211.  
  212. /**
  213. * Returns the sum of all values for the series.
  214. *
  215. * @method getTotalValues
  216. * @return Number
  217. */
  218. getTotalValues: function()
  219. {
  220. var total = this.get("valueAxis").getTotalByKey(this.get("valueKey"));
  221. return total;
  222. },
  223.  
  224. /**
  225. * Draws the series. Overrides the base implementation.
  226. *
  227. * @method draw
  228. * @protected
  229. */
  230. draw: function()
  231. {
  232. var w = this.get("width"),
  233. h = this.get("height");
  234. if(isFinite(w) && isFinite(h) && w > 0 && h > 0)
  235. {
  236. this._rendered = true;
  237. if(this._drawing)
  238. {
  239. this._callLater = true;
  240. return;
  241. }
  242. this._drawing = true;
  243. this._callLater = false;
  244. this.drawSeries();
  245. this._drawing = false;
  246. if(this._callLater)
  247. {
  248. this.draw();
  249. }
  250. else
  251. {
  252. this.fire("drawingComplete");
  253. }
  254. }
  255. },
  256.  
  257. /**
  258. * Draws the markers
  259. *
  260. * @method drawPlots
  261. * @protected
  262. */
  263. drawPlots: function()
  264. {
  265. var values = this.get("valueAxis").getDataByKey(this.get("valueKey")).concat(),
  266. totalValue = 0,
  267. itemCount = values.length,
  268. styles = this.get("styles").marker,
  269. fillColors = styles.fill.colors,
  270. fillAlphas = styles.fill.alphas || ["1"],
  271. borderColors = styles.border.colors,
  272. borderWeights = [styles.border.weight],
  273. borderAlphas = [styles.border.alpha],
  274. tbw = borderWeights.concat(),
  275. tbc = borderColors.concat(),
  276. tba = borderAlphas.concat(),
  277. tfc,
  278. tfa,
  279. padding = styles.padding,
  280. graphic = this.get("graphic"),
  281. minDimension = Math.min(graphic.get("width"), graphic.get("height")),
  282. w = minDimension - (padding.left + padding.right),
  283. h = minDimension - (padding.top + padding.bottom),
  284. startAngle = -90,
  285. halfWidth = w / 2,
  286. halfHeight = h / 2,
  287. radius = Math.min(halfWidth, halfHeight),
  288. i = 0,
  289. value,
  290. angle = 0,
  291. lc,
  292. la,
  293. lw,
  294. wedgeStyle,
  295. marker,
  296. graphOrder = this.get("graphOrder") || 0,
  297. isCanvas = Y.Graphic.NAME === "canvasGraphic";
  298. for(; i < itemCount; ++i)
  299. {
  300. value = parseFloat(values[i]);
  301.  
  302. values.push(value);
  303. if(!isNaN(value))
  304. {
  305. totalValue += value;
  306. }
  307. }
  308.  
  309. tfc = fillColors ? fillColors.concat() : null;
  310. tfa = fillAlphas ? fillAlphas.concat() : null;
  311. this._createMarkerCache();
  312. if(isCanvas)
  313. {
  314. this._setMap();
  315. this._image.width = w;
  316. this._image.height = h;
  317. }
  318. for(i = 0; i < itemCount; i++)
  319. {
  320. value = values[i];
  321. if(totalValue === 0)
  322. {
  323. angle = 360 / values.length;
  324. }
  325. else
  326. {
  327. angle = 360 * (value / totalValue);
  328. }
  329. if(tfc && tfc.length < 1)
  330. {
  331. tfc = fillColors.concat();
  332. }
  333. if(tfa && tfa.length < 1)
  334. {
  335. tfa = fillAlphas.concat();
  336. }
  337. if(tbw && tbw.length < 1)
  338. {
  339. tbw = borderWeights.concat();
  340. }
  341. if(tbw && tbc.length < 1)
  342. {
  343. tbc = borderColors.concat();
  344. }
  345. if(tba && tba.length < 1)
  346. {
  347. tba = borderAlphas.concat();
  348. }
  349. lw = tbw ? tbw.shift() : null;
  350. lc = tbc ? tbc.shift() : null;
  351. la = tba ? tba.shift() : null;
  352. startAngle += angle;
  353. wedgeStyle = {
  354. border: {
  355. color:lc,
  356. weight:lw,
  357. alpha:la
  358. },
  359. fill: {
  360. color:tfc ? tfc.shift() : this._getDefaultColor(i, "slice"),
  361. alpha:tfa ? tfa.shift() : null
  362. },
  363. type: "pieslice",
  364. arc: angle,
  365. radius: radius,
  366. startAngle: startAngle,
  367. cx: halfWidth,
  368. cy: halfHeight,
  369. width: w,
  370. height: h
  371. };
  372. marker = this.getMarker(wedgeStyle, graphOrder, i);
  373. if(isCanvas)
  374. {
  375. this._addHotspot(wedgeStyle, graphOrder, i);
  376. }
  377. }
  378. this._clearMarkerCache();
  379. },
  380.  
  381. /**
  382. * @protected
  383. *
  384. * Method used by `styles` setter. Overrides base implementation.
  385. *
  386. * @method _setStyles
  387. * @param {Object} newStyles Hash of properties to update.
  388. * @return Object
  389. */
  390. _setStyles: function(val)
  391. {
  392. if(!val.marker)
  393. {
  394. val = {marker:val};
  395. }
  396. val = this._parseMarkerStyles(val);
  397. return Y.PieSeries.superclass._mergeStyles.apply(this, [val, this._getDefaultStyles()]);
  398. },
  399.  
  400. /**
  401. * Adds an interactive map when rendering in canvas.
  402. *
  403. * @method _addHotspot
  404. * @param {Object} cfg Object containing data used to draw the hotspot
  405. * @param {Number} seriesIndex Index of series in the `seriesCollection`.
  406. * @param {Number} index Index of the marker using the hotspot.
  407. * @private
  408. */
  409. _addHotspot: function(cfg, seriesIndex, index)
  410. {
  411. var areaNode = DOCUMENT.createElement("area"),
  412. i = 1,
  413. x = cfg.cx,
  414. y = cfg.cy,
  415. arc = cfg.arc,
  416. startAngle = cfg.startAngle - arc,
  417. endAngle = cfg.startAngle,
  418. radius = cfg.radius,
  419. ax = x + Math.cos(startAngle / 180 * Math.PI) * radius,
  420. ay = y + Math.sin(startAngle / 180 * Math.PI) * radius,
  421. bx = x + Math.cos(endAngle / 180 * Math.PI) * radius,
  422. by = y + Math.sin(endAngle / 180 * Math.PI) * radius,
  423. numPoints = Math.floor(arc/10) - 1,
  424. divAngle = (arc/(Math.floor(arc/10)) / 180) * Math.PI,
  425. angleCoord = Math.atan((ay - y)/(ax - x)),
  426. pts = x + ", " + y + ", " + ax + ", " + ay,
  427. cosAng,
  428. sinAng,
  429. multDivAng;
  430. for(i = 1; i <= numPoints; ++i)
  431. {
  432. multDivAng = divAngle * i;
  433. cosAng = Math.cos(angleCoord + multDivAng);
  434. sinAng = Math.sin(angleCoord + multDivAng);
  435. if(startAngle <= 90)
  436. {
  437. pts += ", " + (x + (radius * Math.cos(angleCoord + (divAngle * i))));
  438. pts += ", " + (y + (radius * Math.sin(angleCoord + (divAngle * i))));
  439. }
  440. else
  441. {
  442. pts += ", " + (x - (radius * Math.cos(angleCoord + (divAngle * i))));
  443. pts += ", " + (y - (radius * Math.sin(angleCoord + (divAngle * i))));
  444. }
  445. }
  446. pts += ", " + bx + ", " + by;
  447. pts += ", " + x + ", " + y;
  448. this._map.appendChild(areaNode);
  449. areaNode.setAttribute("class", SERIES_MARKER);
  450. areaNode.setAttribute("id", "hotSpot_" + seriesIndex + "_" + index);
  451. areaNode.setAttribute("shape", "polygon");
  452. areaNode.setAttribute("coords", pts);
  453. this._areaNodes.push(areaNode);
  454.  
  455. },
  456.  
  457. /**
  458. * Resizes and positions markers based on a mouse interaction.
  459. *
  460. * @method updateMarkerState
  461. * @param {String} type state of the marker
  462. * @param {Number} i index of the marker
  463. * @protected
  464. */
  465. updateMarkerState: function(type, i)
  466. {
  467. if(this._markers[i])
  468. {
  469. var state = this._getState(type),
  470. markerStyles,
  471. indexStyles,
  472. marker = this._markers[i],
  473. styles = this.get("styles").marker;
  474. markerStyles = state === "off" || !styles[state] ? styles : styles[state];
  475. indexStyles = this._mergeStyles(markerStyles, {});
  476. indexStyles.fill.color = indexStyles.fill.colors[i % indexStyles.fill.colors.length];
  477. indexStyles.fill.alpha = indexStyles.fill.alphas[i % indexStyles.fill.alphas.length];
  478. marker.set(indexStyles);
  479. }
  480. },
  481.  
  482. /**
  483. * Creates a shape to be used as a marker.
  484. *
  485. * @method _createMarker
  486. * @param {Object} styles Hash of style properties.
  487. * @return Shape
  488. * @private
  489. */
  490. _createMarker: function(styles)
  491. {
  492. var graphic = this.get("graphic"),
  493. marker,
  494. cfg = this._copyObject(styles);
  495. marker = graphic.addShape(cfg);
  496. marker.addClass(SERIES_MARKER);
  497. return marker;
  498. },
  499.  
  500. /**
  501. * Creates a cache of markers for reuse.
  502. *
  503. * @method _createMarkerCache
  504. * @private
  505. */
  506. _clearMarkerCache: function()
  507. {
  508. var len = this._markerCache.length,
  509. i = 0,
  510. marker;
  511. for(; i < len; ++i)
  512. {
  513. marker = this._markerCache[i];
  514. if(marker)
  515. {
  516. marker.destroy();
  517. }
  518. }
  519. this._markerCache = [];
  520. },
  521.  
  522. /**
  523. * Gets the default style values for the markers.
  524. *
  525. * @method _getPlotDefaults
  526. * @return Object
  527. * @private
  528. */
  529. _getPlotDefaults: function()
  530. {
  531. var defs = {
  532. padding:{
  533. top: 0,
  534. left: 0,
  535. right: 0,
  536. bottom: 0
  537. },
  538. fill:{
  539. alphas:["1"]
  540. },
  541. border: {
  542. weight: 0,
  543. alpha: 1
  544. }
  545. };
  546. defs.fill.colors = this._defaultSliceColors;
  547. defs.border.colors = this._defaultBorderColors;
  548. return defs;
  549. }
  550. }, {
  551. ATTRS: {
  552. /**
  553. * Read-only attribute indicating the type of series.
  554. *
  555. * @attribute type
  556. * @type String
  557. * @default pie
  558. */
  559. type: {
  560. value: "pie"
  561. },
  562.  
  563. /**
  564. * Order of this instance of this `type`.
  565. *
  566. * @attribute order
  567. * @type Number
  568. */
  569. order: {},
  570.  
  571. /**
  572. * Reference to the `Graph` in which the series is drawn into.
  573. *
  574. * @attribute graph
  575. * @type Graph
  576. */
  577. graph: {},
  578.  
  579. /**
  580. * Reference to the `Axis` instance used for assigning
  581. * category values to the graph.
  582. *
  583. * @attribute categoryAxis
  584. * @type Axis
  585. */
  586. categoryAxis: {
  587. value: null,
  588.  
  589. validator: function(value)
  590. {
  591. return value !== this.get("categoryAxis");
  592. }
  593. },
  594.  
  595. /**
  596. * Reference to the `Axis` instance used for assigning
  597. * series values to the graph.
  598. *
  599. * @attribute categoryAxis
  600. * @type Axis
  601. */
  602. valueAxis: {
  603. value: null,
  604.  
  605. validator: function(value)
  606. {
  607. return value !== this.get("valueAxis");
  608. }
  609. },
  610.  
  611. /**
  612. * Indicates which array to from the hash of value arrays in
  613. * the category `Axis` instance.
  614. *
  615. * @attribute categoryKey
  616. * @type String
  617. */
  618. categoryKey: {
  619. value: null,
  620.  
  621. validator: function(value)
  622. {
  623. return value !== this.get("categoryKey");
  624. }
  625. },
  626. /**
  627. * Indicates which array to from the hash of value arrays in
  628. * the value `Axis` instance.
  629. *
  630. * @attribute valueKey
  631. * @type String
  632. */
  633. valueKey: {
  634. value: null,
  635.  
  636. validator: function(value)
  637. {
  638. return value !== this.get("valueKey");
  639. }
  640. },
  641.  
  642. /**
  643. * Name used for for displaying category data
  644. *
  645. * @attribute categoryDisplayName
  646. * @type String
  647. */
  648. categoryDisplayName: {
  649. setter: function(val)
  650. {
  651. this._categoryDisplayName = val;
  652. return val;
  653. },
  654.  
  655. getter: function()
  656. {
  657. return this._categoryDisplayName || this.get("categoryKey");
  658. }
  659. },
  660.  
  661. /**
  662. * Name used for for displaying value data
  663. *
  664. * @attribute valueDisplayName
  665. * @type String
  666. */
  667. valueDisplayName: {
  668. setter: function(val)
  669. {
  670. this._valueDisplayName = val;
  671. return val;
  672. },
  673.  
  674. getter: function()
  675. {
  676. return this._valueDisplayName || this.get("valueKey");
  677. }
  678. },
  679.  
  680. /**
  681. * @attribute slices
  682. * @type Array
  683. * @private
  684. */
  685. slices: null
  686.  
  687. /**
  688. * Style properties used for drawing markers. This attribute is inherited from `MarkerSeries`. Below are the default
  689. * values:
  690. * <dl>
  691. * <dt>fill</dt><dd>A hash containing the following values:
  692. * <dl>
  693. * <dt>colors</dt><dd>An array of colors to be used for the marker fills. The color for each marker is
  694. * retrieved from the array below:<br/>
  695. * `["#66007f", "#a86f41", "#295454", "#996ab2", "#e8cdb7", "#90bdbd","#000000","#c3b8ca", "#968373", "#678585"]`
  696. * </dd>
  697. * <dt>alphas</dt><dd>An array of alpha references (Number from 0 to 1) indicating the opacity of each marker
  698. * fill. The default value is [1].</dd>
  699. * </dl>
  700. * </dd>
  701. * <dt>border</dt><dd>A hash containing the following values:
  702. * <dl>
  703. * <dt>color</dt><dd>An array of colors to be used for the marker borders. The color for each marker is
  704. * retrieved from the array below:<br/>
  705. * `["#205096", "#b38206", "#000000", "#94001e", "#9d6fa0", "#e55b00", "#5e85c9", "#adab9e", "#6ac291", "#006457"]`
  706. * <dt>alpha</dt><dd>Number from 0 to 1 indicating the opacity of the marker border. The default value is 1.</dd>
  707. * <dt>weight</dt><dd>Number indicating the width of the border. The default value is 1.</dd>
  708. * </dl>
  709. * </dd>
  710. * <dt>over</dt><dd>hash containing styles for markers when highlighted by a `mouseover` event. The default
  711. * values for each style is null. When an over style is not set, the non-over value will be used. For example,
  712. * the default value for `marker.over.fill.color` is equivalent to `marker.fill.color`.</dd>
  713. * </dl>
  714. *
  715. * @attribute styles
  716. * @type Object
  717. */
  718. }
  719. });
  720.