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