PageRenderTime 151ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/charts/as/com/yahoo/astra/fl/charts/Chart.as

https://github.com/sdesai/yui2
ActionScript | 890 lines | 478 code | 95 blank | 317 comment | 62 complexity | bd4f8bc5a48774046a00bb8d135d33b3 MD5 | raw file
  1. package com.yahoo.astra.fl.charts
  2. {
  3. import com.yahoo.astra.fl.charts.events.ChartEvent;
  4. import com.yahoo.astra.fl.charts.legend.ILegend;
  5. import com.yahoo.astra.fl.charts.legend.LegendItemData;
  6. import com.yahoo.astra.fl.charts.series.ICategorySeries;
  7. import com.yahoo.astra.fl.charts.series.ILegendItemSeries;
  8. import com.yahoo.astra.fl.charts.series.ISeries;
  9. import com.yahoo.astra.fl.charts.series.ISeriesItemRenderer;
  10. import com.yahoo.astra.fl.utils.UIComponentUtil;
  11. import fl.core.InvalidationType;
  12. import fl.core.UIComponent;
  13. import flash.accessibility.AccessibilityProperties;
  14. import flash.display.DisplayObject;
  15. import flash.display.Sprite;
  16. import flash.events.Event;
  17. import flash.events.MouseEvent;
  18. import flash.geom.Point;
  19. import flash.text.TextFormat;
  20. import flash.text.TextFormatAlign;
  21. import flash.utils.getDefinitionByName;
  22. import flash.events.ErrorEvent;
  23. //--------------------------------------
  24. // Styles
  25. //--------------------------------------
  26. /**
  27. * The padding that separates the border of the component from its contents,
  28. * in pixels.
  29. *
  30. * @default 10
  31. */
  32. [Style(name="contentPadding", type="Number")]
  33. /**
  34. * Name of the class to use as the skin for the background and border of the
  35. * component.
  36. *
  37. * @default ChartBackgroundSkin
  38. */
  39. [Style(name="backgroundSkin", type="Class")]
  40. /**
  41. * The default colors for each series. These colors are used for markers,
  42. * in most cases, but they may apply to lines, fills, or other graphical
  43. * items.
  44. *
  45. * <p>An Array of values that correspond to series indices in the data
  46. * provider. If the number of values in the Array is less than the number
  47. * of series, then the next series will restart at index zero in the style
  48. * Array. If the value of this style is an empty Array, then each individual series
  49. * will use the default or modified value set on the series itself.</p>
  50. *
  51. * <p>Example: If the seriesColors style is equal to [0xffffff, 0x000000] and there
  52. * are three series in the chart's data provider, then the series at index 0
  53. * will have a color of 0xffffff, index 1 will have a color of 0x000000, and
  54. * index 2 will have a color of 0xffffff (starting over from the beginning).</p>
  55. *
  56. * @default [0x00b8bf, 0x8dd5e7, 0xedff9f, 0xffa928, 0xc0fff6, 0xd00050, 0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd, 0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77]
  57. */
  58. [Style(name="seriesColors", type="Array")]
  59. /**
  60. * The default size of the markers in pixels. The actual drawn size of the
  61. * markers could end up being different in some cases. For example, bar charts
  62. * and column charts display markers side-by-side, and a chart may need to make
  63. * the bars or columns smaller to fit within the required region.
  64. *
  65. * <p>An Array of values that correspond to series indices in the data
  66. * provider. If the number of values in the Array is less than the number
  67. * of series, then the next series will restart at index zero in the style
  68. * Array. If the value of this style is an empty Array, then each individual series
  69. * will use the default or modified value set on the series itself.</p>
  70. *
  71. * <p>Example: If the seriesMarkerSizes style is equal to [10, 15] and there
  72. * are three series in the chart's data provider, then the series at index 0
  73. * will have a marker size of 10, index 1 will have a marker size of 15, and
  74. * index 2 will have a marker size of 10 (starting over from the beginning).</p>
  75. *
  76. * @default []
  77. */
  78. [Style(name="seriesMarkerSizes", type="Array")]
  79. /**
  80. * An Array containing the default skin classes for each series. These classes
  81. * are used to instantiate the marker skins. The values may be fully-qualified
  82. * package and class strings or a reference to the classes themselves.
  83. *
  84. * <p>An Array of values that correspond to series indices in the data
  85. * provider. If the number of values in the Array is less than the number
  86. * of series, then the next series will restart at index zero in the style
  87. * Array. If the value of this style is an empty Array, then each individual series
  88. * will use the default or modified value set on the series itself.</p>
  89. *
  90. * <p>Example: If the seriesMarkerSkins style is equal to [CircleSkin, DiamondSkin] and there
  91. * are three series in the chart's data provider, then the series at index 0
  92. * will have a marker skin of CircleSkin, index 1 will have a marker skin of DiamondSkin, and
  93. * index 2 will have a marker skin of CircleSkin (starting over from the beginning).</p>
  94. *
  95. * @default []
  96. */
  97. [Style(name="seriesMarkerSkins", type="Array")]
  98. /**
  99. * The TextFormat object to use to render data tips.
  100. *
  101. * @default TextFormat("_sans", 11, 0x000000, false, false, false, '', '', TextFormatAlign.LEFT, 0, 0, 0, 0)
  102. */
  103. [Style(name="dataTipTextFormat", type="TextFormat")]
  104. /**
  105. * Name of the class to use as the skin for the background and border of the
  106. * chart's data tip.
  107. *
  108. * @default ChartDataTipBackground
  109. */
  110. [Style(name="dataTipBackgroundSkin", type="Class")]
  111. /**
  112. * If the datatip's content padding is customizable, it will use this value.
  113. * The padding that separates the border of the component from its contents,
  114. * in pixels.
  115. *
  116. * @default 6
  117. */
  118. [Style(name="dataTipContentPadding", type="Number")]
  119. /**
  120. * Determines if data changes should be displayed with animation.
  121. *
  122. * @default true
  123. */
  124. [Style(name="animationEnabled", type="Boolean")]
  125. /**
  126. * Indicates whether embedded font outlines are used to render the text
  127. * field. If this value is true, Flash Player renders the text field by
  128. * using embedded font outlines. If this value is false, Flash Player
  129. * renders the text field by using device fonts.
  130. *
  131. * If you set the embedFonts property to true for a text field, you must
  132. * specify a font for that text by using the font property of a TextFormat
  133. * object that is applied to the text field. If the specified font is not
  134. * embedded in the SWF file, the text is not displayed.
  135. *
  136. * @default false
  137. */
  138. [Style(name="embedFonts", type="Boolean")]
  139. /**
  140. * Functionality common to most charts. Generally, a <code>Chart</code> object
  141. * shouldn't be instantiated directly. Instead, a subclass with a concrete
  142. * implementation should be used. That subclass generally should implement the
  143. * <code>IPlotArea</code> interface.
  144. *
  145. * @author Josh Tynjala
  146. */
  147. public class Chart extends UIComponent
  148. {
  149. //--------------------------------------
  150. // Class Variables
  151. //--------------------------------------
  152. /**
  153. * @private
  154. */
  155. private static var defaultStyles:Object =
  156. {
  157. seriesMarkerSizes: null,
  158. seriesMarkerSkins: null,
  159. seriesColors:
  160. [
  161. 0x00b8bf, 0x8dd5e7, 0xedff9f, 0xffa928, 0xc0fff6, 0xd00050,
  162. 0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd,
  163. 0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77
  164. ],
  165. seriesBorderColors:[],
  166. seriesFillColors:[],
  167. seriesLineColors:[],
  168. seriesBorderAlphas:[1],
  169. seriesFillAlphas:[1],
  170. seriesLineAlphas:[1],
  171. contentPadding: 10,
  172. backgroundSkin: "ChartBackground",
  173. backgroundColor: 0xffffff,
  174. dataTipBackgroundSkin: "ChartDataTipBackground",
  175. dataTipContentPadding: 6,
  176. dataTipTextFormat: new TextFormat("_sans", 11, 0x000000, false, false, false, '', '', TextFormatAlign.LEFT, 0, 0, 0, 0),
  177. animationEnabled: true,
  178. embedFonts: false
  179. };
  180. /**
  181. * @private
  182. */
  183. private static const ALL_SERIES_STYLES:Object =
  184. {
  185. color: "seriesColors",
  186. markerSize: "seriesMarkerSizes",
  187. markerSkin: "seriesMarkerSkins",
  188. borderColor: "seriesBorderColors",
  189. fillColor: "seriesFillColors",
  190. lineColor: "seriesLineColors",
  191. borderAlpha: "seriesBorderAlphas",
  192. fillAlpha: "seriesFillAlphas",
  193. lineAlpha: "seriesLineAlphas"
  194. };
  195. /**
  196. * @private
  197. */
  198. private static const SHARED_SERIES_STYLES:Object =
  199. {
  200. animationEnabled: "animationEnabled"
  201. };
  202. private static const DATA_TIP_STYLES:Object =
  203. {
  204. backgroundSkin: "dataTipBackgroundSkin",
  205. contentPadding: "dataTipContentPadding",
  206. textFormat: "dataTipTextFormat",
  207. embedFonts: "embedFonts"
  208. };
  209. //--------------------------------------
  210. // Class Methods
  211. //--------------------------------------
  212. /**
  213. * @private
  214. * @copy fl.core.UIComponent#getStyleDefinition()
  215. */
  216. public static function getStyleDefinition():Object
  217. {
  218. return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
  219. }
  220. //--------------------------------------
  221. // Constructor
  222. //--------------------------------------
  223. /**
  224. * Constructor.
  225. */
  226. public function Chart()
  227. {
  228. super();
  229. this.accessibilityProperties = new AccessibilityProperties();
  230. this.accessibilityProperties.forceSimple = true;
  231. this.accessibilityProperties.description = "Chart";
  232. }
  233. //--------------------------------------
  234. // Variables and Properties
  235. //--------------------------------------
  236. /**
  237. * @private
  238. * The display object representing the chart background.
  239. */
  240. protected var background:DisplayObject;
  241. /**
  242. * @private
  243. * The area where series are drawn.
  244. */
  245. protected var content:Sprite;
  246. /**
  247. * @private
  248. * The mouse over data tip that displays information about an item on the chart.
  249. */
  250. protected var dataTip:DisplayObject;
  251. /**
  252. * @private
  253. * Storage for the data property. Saves a copy of the unmodified data.
  254. */
  255. private var _dataProvider:Object;
  256. /**
  257. * @private
  258. * Modified version of the stored data.
  259. */
  260. protected var series:Array = [];
  261. [Inspectable(type=Array)]
  262. /**
  263. * @copy com.yahoo.astra.fl.charts.IChart#dataProvider
  264. */
  265. public function get dataProvider():Object
  266. {
  267. return this.series;
  268. }
  269. /**
  270. * @private
  271. */
  272. public function set dataProvider(value:Object):void
  273. {
  274. if(this._dataProvider != value)
  275. {
  276. this._dataProvider = value;
  277. this.invalidate(InvalidationType.DATA);
  278. }
  279. }
  280. /**
  281. * @private
  282. * Storage for the defaultSeriesType property.
  283. */
  284. private var _defaultSeriesType:Class;
  285. /**
  286. * When raw data (like an Array of Numbers) is encountered where an
  287. * ISeries instance is expected, it will be converted to this default
  288. * type. Accepts either a Class instance or a String referencing a
  289. * fully-qualified class name.
  290. */
  291. public function get defaultSeriesType():Object
  292. {
  293. return this._defaultSeriesType;
  294. }
  295. /**
  296. * @private
  297. */
  298. public function set defaultSeriesType(value:Object):void
  299. {
  300. if(!value) return;
  301. var classDefinition:Class = null;
  302. if(value is Class)
  303. {
  304. classDefinition = value as Class;
  305. }
  306. else
  307. {
  308. // borrowed from fl.core.UIComponent#getDisplayObjectInstance()
  309. try
  310. {
  311. classDefinition = getDefinitionByName(value.toString()) as Class;
  312. }
  313. catch(e:Error)
  314. {
  315. try
  316. {
  317. classDefinition = this.loaderInfo.applicationDomain.getDefinition(value.toString()) as Class;
  318. }
  319. catch (e:Error)
  320. {
  321. // Nothing
  322. }
  323. }
  324. }
  325. this._defaultSeriesType = classDefinition;
  326. //no need to redraw.
  327. //if the series have already been created, the user probably wanted it that way.
  328. //we have no way to tell if the user chose a particular series' type or not anyway.
  329. }
  330. private var _lastDataTipRenderer:ISeriesItemRenderer;
  331. /**
  332. * @private
  333. * Storage for the dataTipFunction property.
  334. */
  335. private var _dataTipFunction:Function = defaultDataTipFunction;
  336. /**
  337. * If defined, the chart will call the input function to determine the
  338. * text displayed in the chart's data tip. The function uses the following
  339. * signature:
  340. *
  341. * <p><code>function dataTipFunction(item:Object, index:int, series:ISeries):String</code></p>
  342. */
  343. public function get dataTipFunction():Function
  344. {
  345. return this._dataTipFunction;
  346. }
  347. /**
  348. * @private
  349. */
  350. public function set dataTipFunction(value:Function):void
  351. {
  352. this._dataTipFunction = value;
  353. }
  354. /**
  355. * @private
  356. * Storage for the legend property.
  357. */
  358. private var _legend:ILegend;
  359. /**
  360. * The component that will display a human-readable legend for the chart.
  361. */
  362. public function get legend():ILegend
  363. {
  364. return this._legend;
  365. }
  366. /**
  367. * @private
  368. */
  369. public function set legend(value:ILegend):void
  370. {
  371. this._legend = value;
  372. this.invalidate();
  373. }
  374. /**
  375. * @private
  376. * Storage for legendLabelFunction
  377. */
  378. private var _legendLabelFunction:Function;
  379. /**
  380. * If defined, the chart will call the input function to determine the text displayed in
  381. * in the chart's legend.
  382. */
  383. public function get legendLabelFunction():Function
  384. {
  385. return this._legendLabelFunction;
  386. }
  387. /**
  388. * @private
  389. */
  390. public function set legendLabelFunction(value:Function):void
  391. {
  392. this._legendLabelFunction = value;
  393. }
  394. //--------------------------------------
  395. // Public Methods
  396. //--------------------------------------
  397. /**
  398. * Returns the index within this plot area of the input ISeries object.
  399. *
  400. * @param series a series that is displayed in this plot area.
  401. * @return the index of the input series
  402. */
  403. public function seriesToIndex(series:ISeries):int
  404. {
  405. return this.series.indexOf(series);
  406. }
  407. /**
  408. * Returns the ISeries object at the specified index.
  409. *
  410. * @param index the index of the series to return
  411. * @return the series that appears at the input index or null if out of bounds
  412. */
  413. public function indexToSeries(index:int):ISeries
  414. {
  415. if(index < 0 || index >= this.series.length) return null;
  416. return this.series[index];
  417. }
  418. //--------------------------------------
  419. // Protected Methods
  420. //--------------------------------------
  421. /**
  422. * @private
  423. */
  424. override protected function configUI():void
  425. {
  426. super.width = 400;
  427. super.height = 300;
  428. super.configUI();
  429. this.content = new Sprite();
  430. this.addChild(this.content);
  431. this.dataTip = new DataTipRenderer();
  432. this.dataTip.visible = false;
  433. this.addChild(this.dataTip);
  434. }
  435. /**
  436. * @private
  437. */
  438. override protected function draw():void
  439. {
  440. var dataInvalid:Boolean = this.isInvalid(InvalidationType.DATA);
  441. var stylesInvalid:Boolean = this.isInvalid(InvalidationType.STYLES);
  442. var sizeInvalid:Boolean = this.isInvalid(InvalidationType.SIZE);
  443. if(stylesInvalid || dataInvalid)
  444. {
  445. this.refreshSeries();
  446. }
  447. //update the background if needed
  448. if(stylesInvalid)
  449. {
  450. if(this.background)
  451. {
  452. this.removeChild(this.background);
  453. }
  454. var skinClass:Object = this.getStyleValue("backgroundSkin");
  455. this.background = UIComponentUtil.getDisplayObjectInstance(this, skinClass);
  456. this.addChildAt(this.background, 0);
  457. }
  458. if(this.background && (stylesInvalid || sizeInvalid))
  459. {
  460. this.background.width = this.width;
  461. this.background.height = this.height;
  462. //force the background to redraw if it is a UIComponent
  463. if(this.background is UIComponent)
  464. {
  465. (this.background as UIComponent).drawNow();
  466. }
  467. }
  468. if(this.dataTip is UIComponent)
  469. {
  470. var dataTip:UIComponent = UIComponent(this.dataTip);
  471. this.copyStylesToChild(dataTip, DATA_TIP_STYLES);
  472. dataTip.drawNow();
  473. }
  474. super.draw();
  475. }
  476. /**
  477. * Analyzes the input data and smartly converts it to the correct ISeries type
  478. * required for drawing. Adds new ISeries objects to the display list and removes
  479. * unused series objects that no longer need to be drawn.
  480. */
  481. protected function refreshSeries():void
  482. {
  483. var modifiedData:Object = this._dataProvider;
  484. //loop through each series and convert it to the correct data type
  485. if(modifiedData is Array)
  486. {
  487. var arrayData:Array = (modifiedData as Array).concat();
  488. var seriesCount:int = arrayData.length;
  489. var foundIncompatibleData:Boolean = false;
  490. for(var i:int = 0; i < seriesCount; i++)
  491. {
  492. var currentItem:Object = arrayData[i];
  493. if(currentItem is Array || currentItem is XMLList)
  494. {
  495. var itemSeries:ISeries = new this.defaultSeriesType();
  496. if(currentItem is Array)
  497. {
  498. itemSeries.dataProvider = (currentItem as Array).concat();
  499. }
  500. else if(currentItem is XMLList)
  501. {
  502. itemSeries.dataProvider = (currentItem as XMLList).copy();
  503. }
  504. arrayData[i] = itemSeries;
  505. }
  506. else if(!(currentItem is ISeries))
  507. {
  508. //we only support Array, XMLList, and ISeries
  509. //anything else means that we should restore the original data
  510. var originalData:Array = (modifiedData as Array).concat();
  511. modifiedData = new this.defaultSeriesType(originalData);
  512. foundIncompatibleData = true;
  513. break;
  514. }
  515. }
  516. if(!foundIncompatibleData)
  517. {
  518. modifiedData = arrayData;
  519. }
  520. }
  521. //attempt to turn a string into XML
  522. if(modifiedData is String)
  523. {
  524. try
  525. {
  526. modifiedData = new XML(modifiedData);
  527. }
  528. catch(error:Error)
  529. {
  530. //this isn't a valid xml string, so ignore it
  531. return;
  532. }
  533. }
  534. //we need an XMLList, so get the elements
  535. if(modifiedData is XML)
  536. {
  537. modifiedData = (modifiedData as XML).elements();
  538. }
  539. //convert the XMLList to a series
  540. if(modifiedData is XMLList)
  541. {
  542. modifiedData = new this.defaultSeriesType(modifiedData);
  543. }
  544. //we should have an ISeries object by now, so put it in an Array
  545. if(modifiedData is ISeries)
  546. {
  547. //if the main data is a series, put it in an array
  548. modifiedData = [modifiedData];
  549. }
  550. //if it's not an array, we have bad data, so ignore it
  551. if(!(modifiedData is Array))
  552. {
  553. return;
  554. }
  555. arrayData = modifiedData as Array;
  556. seriesCount = this.series.length;
  557. for(i = 0; i < seriesCount; i++)
  558. {
  559. var currentSeries:ISeries = this.series[i] as ISeries;
  560. if(arrayData.indexOf(currentSeries) < 0)
  561. {
  562. //if the series no longer exists, remove it from the display list and stop listening to it
  563. this.content.removeChild(DisplayObject(currentSeries));
  564. currentSeries.removeEventListener("dataChange", seriesDataChangeHandler);
  565. currentSeries.removeEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemRollOver);
  566. currentSeries.removeEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemRollOut);
  567. currentSeries.chart = null;
  568. }
  569. }
  570. //rebuild the series Array
  571. this.series = [];
  572. seriesCount = arrayData.length;
  573. for(i = 0; i < seriesCount; i++)
  574. {
  575. currentSeries = arrayData[i] as ISeries;
  576. this.series.push(currentSeries);
  577. if(!this.contains(DisplayObject(currentSeries)))
  578. {
  579. //if this is a new series, add it to the display list and listen for events
  580. currentSeries.addEventListener("dataChange", seriesDataChangeHandler, false, 0, true);
  581. currentSeries.addEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemRollOver, false, 0, true);
  582. currentSeries.addEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemRollOut, false, 0, true);
  583. currentSeries.chart = this;
  584. this.content.addChild(DisplayObject(currentSeries));
  585. }
  586. DisplayObject(currentSeries).x = 0;
  587. DisplayObject(currentSeries).y = 0;
  588. //make sure the series are displayed in the correct order
  589. this.content.setChildIndex(DisplayObject(currentSeries), this.content.numChildren - 1);
  590. //update the series styles
  591. this.copyStylesToSeries(currentSeries, ALL_SERIES_STYLES);
  592. if(currentSeries is UIComponent)
  593. {
  594. this.copyStylesToChild(UIComponent(currentSeries), SHARED_SERIES_STYLES);
  595. }
  596. }
  597. }
  598. /**
  599. * @private
  600. * Refreshes the legend's data provider.
  601. */
  602. protected function updateLegend():void
  603. {
  604. if(!this.legend) return;
  605. var legendData:Array = [];
  606. var seriesCount:int = this.series.length;
  607. for(var i:int = 0; i < seriesCount; i++)
  608. {
  609. var series:ISeries = ISeries(this.series[i]);
  610. if(series is ILegendItemSeries)
  611. {
  612. if(!(series as ILegendItemSeries).showInLegend) continue;
  613. var itemData:LegendItemData = ILegendItemSeries(series).createLegendItemData();
  614. itemData.label = itemData.label ? itemData.label : i.toString();
  615. if(series.legendLabelFunction != null && series.legendLabelFunction is Function)
  616. {
  617. try
  618. {
  619. itemData.label = series.legendLabelFunction(itemData.label);
  620. }
  621. catch(e:Error)
  622. {
  623. var message:String = "There is an error in the series level legendLabelFunction.";
  624. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
  625. }
  626. }
  627. else if(this.legendLabelFunction != null && this.legendLabelFunction is Function)
  628. {
  629. try
  630. {
  631. message = "There is an error in the legendLabelFunction.";
  632. itemData.label = this.legendLabelFunction(itemData.label);
  633. }
  634. catch(e:Error)
  635. {
  636. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
  637. }
  638. }
  639. legendData.push(itemData);
  640. }
  641. else if(series is ICategorySeries)
  642. {
  643. legendData = legendData.concat(ICategorySeries(series).createLegendItemData());
  644. }
  645. }
  646. this.legend.dataProvider = legendData;
  647. if(UIComponent.inCallLaterPhase)
  648. {
  649. UIComponent(this.legend).drawNow();
  650. }
  651. }
  652. /**
  653. * @private
  654. * Tranfers the chart's styles to the ISeries components it contains. These styles
  655. * must be of the type Array, and the series index determines the index of the value
  656. * to use from that Array. If the chart contains more ISeries components than there
  657. * are values in the Array, the indices are reused starting from zero.
  658. */
  659. protected function copyStylesToSeries(child:ISeries, styleMap:Object):void
  660. {
  661. var index:int = this.series.indexOf(child);
  662. var childComponent:UIComponent = child as UIComponent;
  663. for(var n:String in styleMap)
  664. {
  665. var styleValues:Array = this.getStyleValue(styleMap[n]) as Array;
  666. //if it doesn't exist, ignore it and go with the defaults for this series
  667. if(styleValues == null || styleValues.length == 0) continue;
  668. childComponent.setStyle(n, styleValues[index % styleValues.length])
  669. }
  670. }
  671. /**
  672. * @private
  673. */
  674. protected function defaultDataTipFunction(item:Object, index:int, series:ISeries):String
  675. {
  676. if(series.displayName)
  677. {
  678. return series.displayName;
  679. }
  680. return "";
  681. }
  682. /**
  683. * @private
  684. * Passes data to the data tip.
  685. */
  686. protected function refreshDataTip():void
  687. {
  688. var item:Object = this._lastDataTipRenderer.data;
  689. var series:ISeries = this._lastDataTipRenderer.series;
  690. var index:int = series.itemRendererToIndex(this._lastDataTipRenderer);
  691. var dataTipText:String = "";
  692. if(series.dataTipFunction != null)
  693. {
  694. try
  695. {
  696. dataTipText = series.dataTipFunction(item, index, series);
  697. }
  698. catch(e:Error)
  699. {
  700. var message:String = "There is an error in your series level dataTipFunction";
  701. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
  702. }
  703. }
  704. else if(this.dataTipFunction != null)
  705. {
  706. try
  707. {
  708. dataTipText = this.dataTipFunction(item, index, series);
  709. }
  710. catch(e:Error)
  711. {
  712. message = "There is an error in your dataTipFunction";
  713. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
  714. }
  715. }
  716. var dataTipRenderer:IDataTipRenderer = this.dataTip as IDataTipRenderer;
  717. dataTipRenderer.text = dataTipText;
  718. dataTipRenderer.data = item;
  719. this.setChildIndex(this.dataTip, this.numChildren - 1);
  720. if(this.dataTip is UIComponent)
  721. {
  722. UIComponent(this.dataTip).drawNow();
  723. }
  724. }
  725. //--------------------------------------
  726. // Protected Event Handlers
  727. //--------------------------------------
  728. /**
  729. * @private
  730. * Display the data tip when the user moves the mouse over a chart marker.
  731. */
  732. protected function chartItemRollOver(event:ChartEvent):void
  733. {
  734. this._lastDataTipRenderer = event.itemRenderer;
  735. this.refreshDataTip();
  736. var position:Point = this.mousePositionToDataTipPosition();
  737. this.dataTip.x = position.x;
  738. this.dataTip.y = position.y;
  739. this.dataTip.visible = true;
  740. this.stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler, false, 0 ,true);
  741. }
  742. /**
  743. * @private
  744. * Hide the data tip when the user moves the mouse off a chart marker.
  745. */
  746. protected function chartItemRollOut(event:ChartEvent):void
  747. {
  748. this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);
  749. this.dataTip.visible = false;
  750. }
  751. //--------------------------------------
  752. // Private Methods
  753. //--------------------------------------
  754. /**
  755. * @private
  756. * Determines the position for the data tip based on the mouse position
  757. * and the bounds of the chart. Attempts to keep the data tip within the
  758. * chart bounds so that it isn't hidden by any other display objects.
  759. */
  760. private function mousePositionToDataTipPosition():Point
  761. {
  762. var position:Point = new Point();
  763. position.x = this.mouseX + 2;
  764. position.x = Math.min(this.width - this.dataTip.width, position.x);
  765. position.y = this.mouseY - this.dataTip.height - 2;
  766. position.y = Math.max(0, position.y);
  767. return position;
  768. }
  769. //--------------------------------------
  770. // Private Event Handlers
  771. //--------------------------------------
  772. /**
  773. * @private
  774. * The plot area needs to redraw the axes if series data changes.
  775. */
  776. private function seriesDataChangeHandler(event:Event):void
  777. {
  778. this.invalidate(InvalidationType.DATA);
  779. if(this.dataTip.visible)
  780. {
  781. this.refreshDataTip();
  782. }
  783. }
  784. /**
  785. * @private
  786. * Make the data tip follow the mouse.
  787. */
  788. private function stageMouseMoveHandler(event:MouseEvent):void
  789. {
  790. var position:Point = this.mousePositionToDataTipPosition();
  791. this.dataTip.x = position.x;
  792. this.dataTip.y = position.y;
  793. }
  794. }
  795. }