Friday, April 26, 2013

Flex: Chart Data interaction

There might be numerous situations where you have to interact with a chart data and at the same time, perform some type of actions. Therefore, I'm going to show you how you can perform a simple action upon clicking on a chart data.

Here's the source code of my main application - SimpleChartDataClick.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:Declarations>
  <!-- 
  We need to specify the types of animations over here
  -->
  <s:Parallel id="parallelEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
   <mx:SeriesSlide duration="1000" direction="up"/>
  </s:Parallel>
  <s:Parallel id="parallelCarEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
  </s:Parallel>
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import flashx.textLayout.conversion.TextConverter;
   
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.chartClasses.Series;
   import mx.charts.events.ChartItemEvent;
   import mx.charts.series.ColumnSeries;
   import mx.charts.series.LineSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   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:ColumnSeries = new ColumnSeries();
   private var localCarSeries:ColumnSeries = new ColumnSeries();
   
   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 alternate colors for the columns
    localSeries.setStyle("fill", 0xCDFFCD);
    //Create the strokes for the columns
    localSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));
    localSeries.displayName = "col_people";
    //Set the width(%) of the column 
    localSeries.columnWidthRatio = 0.3;
    //Set the offset
    localSeries.offset = -0.15;
    
    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 alternate colors for the columns
    localCarSeries.setStyle("fill", 0xCDCDFF);
    //Create the strokes for the columns
    localCarSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));
    localCarSeries.displayName = "line_car";
    //Set the width(%) of the column 
    localCarSeries.columnWidthRatio = 0.3;
    //Set the offset
    localCarSeries.offset = 0.15;
    
    //We will remove all the series attach to the chart
    //first
    chart.series = null;
    
    //End all the effects first, else some glich will
    //appear.
    parallelEffect.end();
    parallelCarEffect.end();
    
    //Base on the type of animation selected, attach 
    //the effect to the column
    localSeries.setStyle("creationCompleteEffect", 
     parallelEffect);
    localCarSeries.setStyle("creationCompleteEffect", 
     parallelCarEffect);
    
    // 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 == "col_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;
   }  
   
   //This function will toggle the visibility of the chart
   //Data based on the values of the respective check boxes.
   protected function chkChartChangeEvent(event:Event):void
   {
    var showCarData:Boolean = chkCar.selected;
    var showPeopleData:Boolean = chkPeople.selected;
    for(var i:int = 0; i < chart.series.length; i ++)
    {
     if(chart.series[i].displayName == "line_car")
     {
      chart.series[i].visible = showCarData;
     }
     if(chart.series[i].displayName == "col_people")
     {
      chart.series[i].visible = showPeopleData;
     }
    }
   }
   
   //Upon clicking on one of the columns, this function
   //will update the label 'lblResult' based on the data
   //from the selected chart item.
   protected function itemClickEvent(e:ChartItemEvent):void
   {
    var str:String = "You have click on the item on ";
    var tempDate:Date = new Date();
    tempDate.time = e.hitData.item.date.time;
    str += dateFormatter(tempDate) + " with the data ";
    var tempValue:String = "";
    if(Series(e.hitData.element).displayName == "col_people")
    {
     str += "People: "; 
     tempValue = e.hitData.item.people;
    }else{
     str += "Car: ";
     tempValue = e.hitData.item.car;
    }
    str += tempValue;
    lblResult.text = str;
   }
   
  ]]>
 </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%"
           itemClick="itemClickEvent(event)"
           dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="false" 
           parseFunction="createDate"/> 
     </mx:horizontalAxis>
    </mx:CartesianChart>
   </s:HGroup>
  </s:BorderContainer>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:CheckBox id="chkCar"
      change="chkChartChangeEvent(event)" 
      selected="true"
      label="Show Car Data"/>
   <s:CheckBox id="chkPeople"
      change="chkChartChangeEvent(event)" 
      selected="true"
      label="Show People Data"/>
  </s:HGroup>
  <s:Spacer height="10"/>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:Label text="For your info:"/>
  </s:HGroup>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:Label id="lblResult"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.
~ Click here to find out more about Chart Data Interactions in Flex.

Saturday, April 20, 2013

Apache Ant: Executing a target in another target

As my current workplace we were using 'Jenkins' and 'Apache Ant' to automate and release the updated files of the respective projects once in a while, therefore I ended up trying out something on the 'Apache Ant' script. :P

So how to run a 'target' in another 'target'?
First of all, you probably have a 'target' like the following.
 <target name="building" description="Building a project">
 </target>
And then, you probably have a added a 'sequential' node, because you wanted to execute a series of tasks.
 <target name="building" description="Building a project">
  <sequential>
  </sequential>
 </target>
So how to execute another 'target' in the current 'target'? Well you can either add a 'antcall' or a 'antcallback' node. But what's the difference between the both of them? 'antcall' will execute another 'target' while 'antcallback' will execute another 'target' but at the same time allows you to return the 'property' that was created in the executed 'target'.
 <target name="building" description="Building a project">
  <sequential>
   <antcallback target="generateTimeStamp" return="timestamp"/>
   <antcall target="echoMsg"/>
  </sequential>
 </target>
Finally, you might end up with something like the following. I'm running 2 'target' in another 'target'. The 1st 'target' will create a 'property' and the 'property' will be passed into the 2nd 'target' and 'echo' the value of the 'property' as a message.
 <target name="building" description="Building a project">
  <sequential>
   <antcallback target="generateTimeStamp" return="timestamp"/>
   <antcall target="echoMsg"/>
  </sequential>
 </target>

 <target name="generateTimeStamp">
  <tstamp>
   <format property="timestamp" pattern="ddMMyyyyHHmmss"/>
  </tstamp>
 </target>
 
 <target name="echoMsg">
  <echo message="Generated Timestamp: ${timestamp}"/>
 </target>
* Click here to find out more about 'Jenkins'.
^ Click here to find out more about 'Apache Ant'.

Saturday, April 13, 2013

FB + Adobe AIR: Posting Bug

My friends were asking me to help them to debug an interest problem that they are facing with the Facebook Graph Desktop API.

The scenario as follows. A user log in to the desktop application (Adobe AIR) through Facebook and he decided to post something on his Facebook wall through the application and decides to log out of the Facebook and Application. If a second user tries to log in and post something to his Facebook wall using the application, the message will appear on the wall on the first user, rather than the second user. Therefore here's a fix to that issue if you are using the source files of the 'Facebook Graph Desktop API'.
You have to modify the following function of the following file - com\facebook\graph\FacebookDesktop.as From
    public static function api(method:String,
                     callback:Function,
                     params:* = null,
                     requestMethod:String = 'GET'
    ):void {
      getInstance().api(method,
        callback,
        params,
        requestMethod
      );
    }
To
    public static function api(method:String,
                     callback:Function,
                     params:* = null,
                     requestMethod:String = 'GET'
    ):void {

      if(params != null)
      {
        if (getInstance().session != null) {
          params.access_token = getInstance().session.accessToken;
        }
      }
      getInstance().api(method,
        callback,
        params,
        requestMethod
      );
    }
* Click here for the updated file 'FacebookDesktop.as'.
^ Click here to find out more about the 'Facebook Graph Desktop API'.