Friday, December 14, 2012

Flex: Changing the border colour of a Tool Tip of a Area Series

You might have encounter the following issue. You have created a few Charts with a combination of numerous Area Series. However, the border colour of the tool tip is taken from the area fill of the Area Series and you wanted to change the border colour of the tool tip into the stroke colour of the Area Series. And here's a simple solution to the scenario stated in the above.

Let's take a look at the main application file first.

SimpleAreaChartToolTip.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)"> 
 <fx:Script>
  <![CDATA[
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.chartClasses.Series;
   import mx.charts.series.AreaSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   import mx.graphics.GradientEntry;
   import mx.graphics.LinearGradient;
   import mx.graphics.SolidColorStroke;
   import mx.graphics.Stroke;
   
   import spark.events.IndexChangeEvent;
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records> 
     <record>
      <date>01/09/2013</date>
      <people>63</people>
      <car>23</car>
     </record>
     <record>
      <date>02/09/2013</date>
      <people>61</people>
      <car>81</car>
     </record>
     <record>
      <date>03/09/2013</date>
      <people>67</people>
      <car>47</car>
     </record>
     <record>
      <date>04/09/2013</date>
      <people>75</people>
      <car>95</car>
     </record>
     <record>
      <date>05/09/2013</date>
      <people>65</people>
      <car>45</car>
     </record>
     <record>
      <date>06/09/2013</date>
      <people>32</people>
      <car>52</car>
     </record>
     <record>
      <date>07/09/2013</date>
      <people>66</people>
      <car>46</car>
     </record>
     <record>
      <date>08/09/2013</date>
      <people>85</people>
      <car>105</car>
     </record>
     <record>
      <date>09/09/2013</date>
      <people>37</people>
      <car>57</car>
     </record>
     <record>
      <date>10/09/2013</date>
      <people>80</people>
      <car>100</car>
     </record>
    </records>;
   
   private var localSeries:AreaSeries = new AreaSeries();
   private var localSeriesFill:LinearGradient = 
    new LinearGradient();
   private var localSeriesFillArray:Array = 
    new Array();
   private var localSeriesStroke:SolidColorStroke = 
    new SolidColorStroke();
   
   private var localCarSeries:AreaSeries = new AreaSeries();
   private var localCarSeriesFill:LinearGradient = 
    new LinearGradient();
   private var localCarSeriesFillArray:Array = 
    new Array();
   private var localCarSeriesStroke:SolidColorStroke = 
    new SolidColorStroke();
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    
    //Create the SortField object for the "time" field in 
    //the ArrayCollection object, and make sure we do a 
    //numeric sort.
    var dataSortField:SortField = new SortField();
    dataSortField.name = "time";
    dataSortField.numeric = true;
    
    //Create the Sort object and add the SortField object 
    //created earlier to the array of fields to sort on.
    var numericDataSort:Sort = new Sort();
    numericDataSort.fields = [dataSortField];
    
    // Parsing the xml data into ArrayCollection
    var objArray:ArrayCollection = new ArrayCollection();
    var tempObj:Object;
    var dateArray:Array;
    var tempDate:Date;
    for(var i:int = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.people = myData.record[i].people;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    }
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localSeries.dataProvider = objArray;
    localSeries.yField = "people";
    localSeries.xField = "date";
    
    //Create the gradient for the first Area Series
    var tempFill:GradientEntry = 
     new GradientEntry(0x999999, 0, 0.5);
    localSeriesFillArray.push(tempFill);
    tempFill = 
     new GradientEntry(0x000000, 1, 0.5);
    localSeriesFillArray.push(tempFill);
    localSeriesFill.entries = localSeriesFillArray;
    localSeries.setStyle("areaFill", localSeriesFill);
    
    //Create the Stroke for the first Area Series
    localSeriesStroke = new SolidColorStroke(0xFF0000, 2, 0.5);
    localSeries.setStyle("areaStroke", localSeriesStroke);
    
    localSeries.displayName = "area_people";
    
    objArray = new ArrayCollection();
    for(i = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.car = myData.record[i].car;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    } 
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localCarSeries.dataProvider = objArray;
    localCarSeries.yField = "car";
    localCarSeries.xField = "date";
    
    //Create the gradient for the second Area Series
    tempFill = 
     new GradientEntry(0xFF7300, 0, 0.8);
    localCarSeriesFillArray.push(tempFill);
    tempFill = 
     new GradientEntry(0xFFBB00, 1, 0.5);
    localCarSeriesFillArray.push(tempFill);
    localCarSeriesFill.entries = localCarSeriesFillArray;
    localCarSeries.setStyle("areaFill", localCarSeriesFill);
    
    //Create the Stroke for the second Area Series
    localCarSeriesStroke = new SolidColorStroke(0x00FF00, 2, 0.5);
    localCarSeries.setStyle("areaStroke", localCarSeriesStroke);
    localCarSeries.displayName = "area_car";
    
    //We will remove all the series attach to the chart
    //first
    chart.series = null;
    
    // Back up the current series on the chart.
    var currentSeries:Array = chart.series;
    // Add the new series to the current Array of series.
    currentSeries.push(localCarSeries);
    currentSeries.push(localSeries);
    // Add the new Array of series to the chart.
    chart.series = currentSeries;
   }
   
   //This function will return a string based on the
   //Date format DD/MM/YYYY.
   private function dateFormatter(tempDate:Date):String
   {
    var fmt:DateFormatter = new DateFormatter();
    fmt.formatString = "DD/MM/YYYY";
    return fmt.format(tempDate);
   }
   
   //We are customizing the datatip / tool tip of the
   //chart data.
   public function myDataTipFunction(e:HitData):String {
    var s:String = "";
    var tempDate:Date = e.item.date as Date;
    s += "Date: " + dateFormatter(tempDate) + "<br>";
    if(Series(e.element).displayName == "area_people")
    {
     s += "No. of People: " + e.item.people;
    }else{
     s += "No. of Cars: " + e.item.car;
    }
    return s;
   }
   
   //This function will be used to change the date labels of
   //the chart to match the data.
   public function createDate(s:Date):Date {    
    var newDate:Date = new Date();
    newDate.time = s.time;
    //We need to increase a day to the labels.
    newDate.date += 1;
    return newDate;
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:BorderContainer width="100%"
         backgroundAlpha="0"
         borderVisible="false">
   <s:HGroup verticalAlign="middle" horizontalAlign="center"
       width="100%"
       height="100%">
    <!-- Need to set the gutterLeft and 
    gutterTop of the chart -->
    <mx:CartesianChart id="chart"
           gutterTop="0"
           gutterLeft="50"
           showDataTips="true" 
           width="80%"
           height="80%"
           dataTipRenderer="ToolTip.AreaSeriesToolTip"
           dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="false" 
           parseFunction="createDate"/> 
     </mx:horizontalAxis>
    </mx:CartesianChart>
   </s:HGroup>
  </s:BorderContainer>
 </s:VGroup>
</s:Application>

ToolTip Renderer File
ToolTip/AreaSeriesToolTip.as
package ToolTip
{
 import mx.charts.HitData;
 import mx.charts.chartClasses.DataTip;
 import mx.charts.chartClasses.Series;
 
 public class AreaSeriesToolTip extends DataTip
 {
  public function AreaSeriesToolTip()
  {
   super();
  }
  
  override public function set data(value:Object):void
  {
   //Get the HitData
   var dataPoint:HitData = value as HitData;
   //Cast it into a series so that we can find the
   //display name of the series
   var dataElement:Series = Series(dataPoint.element);
   //Based on the display name of the series, change
   //the contextColor accordingly.
   if(dataElement.displayName == "area_people")
   {
    value.contextColor = 0xFF0000;
   }else if(dataElement.displayName == "area_car")
   {
    value.contextColor = 0x00FF00; 
   }
   super.data = value;
  }
 }
}
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

No comments:

Post a Comment