Modified

10 March 2008

Requirements 

   
User level
Intermediate
 
Sample files  
dashboard_ilog.zip (3282 KB)

   

 
Additional Requirements

 
ILOG Elixir components 1.0
With the release of Flex 3 there is a multitude of very exciting new things that Flex developers can do. Some of these new capabilities are visual (importing skins, proper CSS preview), some of them are data-based (.NET/PHP wizards) and others are a mixture of both—such as working with the exciting ILog components.
 
In this article I'll walk you through the process of creating a very simple Flex and Adobe AIR application using just two ILog Elixir components and an external data source. I'll show you how Flex Builder is a development powerhouse that takes the strain and helps you build applications.
 
The application we'll be building is a dashboard that displays a map created from a shape file. Users of the app can utilize the map to navigate to HTML pages, query a web service, and view population statistics in a 3D chart.
 

 
Creating the map

To begin building the application, you'll need a shape file to create the map. A shape file refers to a collection of files with the extensions .shp, .shx, and .dbf. Grouped together, these files provide the vector point and line information used by many GIS applications to draw maps.  The files can also include one or more attributes—which are essentially rows of embedded data.
 
There are many commercial and open-source shape files available. For the purposes of this tutorial, we'll be using one of the many shape files available for download from Geodan's website.
 
After you download the shape file and unzip it, open the Custom Map Converter located in the ILog Elixir installation folder. The screen has two different views: Main and Advanced. In most cases the Main view is sufficient, but there are some interesting elements available in the advanced settings too.
 
The next thing to do is set the input shapefile value. Navigate to the folder where you extracted the Zip file (although there are many files in the folder, the Custom Map Converter specifically looks for *.shp files). Select the file called Landen (which stands for land). You'll see a preview of the map data in the file in the main window. If you click any of the red squares, a drop-down menu appears with all the attribute data available for that location—the example below contains quite a lot, but in many cases it may display less (see Figure 1).
 
A preview of the map data lists all of the attributes for each location
Figure 1. A preview of the map data lists all of the attributes for each location
Now it's time to decide which of the attributes to include in the converted ActionScript as you create the key. The key can is used to provide lookup data and query a range of data sources, which is ideal for creating data visualizations and dashboard applications.
 
For the purposes of this demonstration, select FIPS 10-4. This is the two-character code used by the US Government for geographical data processing (we'll use another one of these later on).
 
Before you produce the file, it's worth pointing out a couple of items on the advanced settings tab. You can specify your own output directory and also choose a different geographical coordinate system from a range of common projections. Tip: try swapping the projection drop-down menu to Azimuthal Equidistant and then to stretographic to get a better idea of the range available. After testing, be sure that you set the menu option back to the default setting of geographic before proceeding.
 
Now let's return to the Main Parameters. You could process this file now, as – is. However, we don't need this level of detail and we can reduce the file size of the application by using a great feature. You can set the simplification threshold to manage the complexity of the data in the form. This process basically removes some of the vector information to create a simpler and more responsive application. Try setting the simplification threshold to about 5000. Be warned: it can take some time to simplify, so you have to wait until the map image has updated. The further you simplify, the more data is lost. For this reason it is helpful to watch the console to see the data that is being discarded (see Figure 2).
 
After setting the simplification threshold to 5000 the amount of data is reduced
Figure 2. After setting the simplification threshold to 5000 the amount of data is reduced
After this operation is complete, press the "Export to Actionscript" option and  sit back and wait (this also takes a little while). Two files are generated. The first, LandenMap, contains an implementation of MapBase, which allows us to use it as a UI element in Flex. The other file, Landen, contains the point and vector information that has been converted into ActionScript to draw the graphics for us. Clever stuff.
 

 
Bringing the map into Flex

In this section we'll take the vector map created in the last part and work with it in Flex. First, create an Adobe AIR project.
 
Note: You could also create a standard Flex project, but we want to use the HTML component down the line, so Adobe AIR is the best.
 
Make sure that you include the Elixir swcs in your project. Navigate to the Library Path tab, select the option to add SWC and then browse to the ILog Elixir installation folder on your desktop. There are two swc files you need to include: /frameworks/libs/ilog-exlixir.swc  (the main classes) and /frameworks./locale/en_us (or ja_JP) /ilog-elixir_rb (the locale-specific resource bundle). Don't forget that you can now set these swcs as runtime shared libraries as well. It just keeps getting better. Alternativly, you could just drag the swcs into your project's libs folder and let Flex do the including for you.
 
Now, import or copy the two ActionScript files created in the last section into your project (to keep the folder structure intact). If you are working with Flex Builder 3, go to the Design View and look in the component window. You should see that your map now appears as a new component. Drag it onto the canvas and run your application. Your map is now complete with the selected functionality built in.
 
If you are not using Flex Builder you can achieve the same goal by using the sample code provided below.
 
Because the Map class extends the MapBase, we can access the features that are built into the pre-bundled Elixir maps. At a very simple level this means we can style the layout and add events to the map: 
 
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:maps="ilog.maps.*"> <maps:LandenMap backgroundFill="0x000000" highlightFill="0xff0000" x="10" y="10" width="440" height="504"> </maps:LandenMap> </mx:WindowedApplication>
Click on the map to see your highlight selection color as it is now displayed.
 
There are a number of built-in events (mapItemRollOver, mapItemRollOut, and mapItemClick) that return a custom event called a MapEvent. The MapEvent event contains a MapFeature object that relates to the data associated with the item that fired the event and – most importantly – the key or attribute that we embedded in the Custom Map Converter.
 
The following code shows how it works. To trigger the event, add an event listener to the MXML for the map:
 
(mapItemClick="handleMapItemClick(event)").
 
Create a script block at the top of the application and add a handler that traces the key that is returned:
 
import ilog.maps.MapEvent; private function handleMapItemClick(ev:MapEvent):void { trace (ev.mapFeature.key) }
Debug the application and click on various countries to double-check that the two character code is successfully returned.
 
The next step is to use this to access the CIA Factbook, a good source of country-specific information. In this example all of the countries are referred to using the FIPS 10-4 standard that we embedded above.
 
To get it to work, create an HTML Component, assign it the ID of ‘pane' and place it in your application. Modify the handler function to set the location of the pane to a unique URL for each country that is clicked by concatenating the base URL of the CIA Factbook with the two character code as shown below:
 
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:maps="ilog.maps.*"> <mx:Script> <![CDATA[ import ilog.maps.MapEvent; import mx.controls.Alert; //the base URL of the CIA factbook private var _stem:String = "https://www.cia.gov/library/publications/the-world-factbook/print/"; /* called whenever a country clicked */ private function handleMapItemClick(ev:MapEvent):void { try { //set the URL of the HTML component to the concatenated URL using // the stem of the CIA Factbook and the two character code pane.location = _stem + ev.mapFeature.key.toLowerCase() + ".html" } catch (er:Error) { //error handling if user clicks somewhere outside the countries Alert.show ("please click on a country") } } ]]> </mx:Script> <maps:LandenMap backgroundFill="0x000000" highlightFill="0xff0000" mapItemClick="handleMapItemClick(event)" x="10" y="10" width="440" height="504"> </maps:LandenMap> <mx:HTML id="pane" width="50%" height="100%" x="458" y="10"/> </mx:WindowedApplication>

 
Getting data from other sources

There are many web services out there providing data on a massive scale that can be queried for your application. In this section we'll add in a web service to return data filtered by the map key.
 
The first thing to do is find the web service that gives you the information you need. Quite often you will need to embed the ISO_3166_2 key (which is created by the International Standardization Organization) rather than the more United States-centric FIPS_10_4. In principle they—and ISO_3166_3, which is a three-character moniker—all work the same. For the purposes of this article, I created a very simple dummy service in ColdFusion, which returns population information for the last 150 years. Keep in mind this is a tiny example of the scope you could reach with your key.
 
Here's the sample web service:
 
<cfcomponent> <cffunction name="getData" access="remote" returntype="xml"> <cfargument name="countryid" type="string" required="yes"> <cfquery datasource="webforgeDev" name="popQuery"> SELECT * FROM tbl_pop WHERE CkEY ='#countryId#' </cfquery> <!--- using Ray Camden's excellent toXML converter> <cfset toXML = createObject("component", "toXML")> <cfset population = toXML.queryToXML(popQuery, "dataset", "row")> <cfset retPopXML = xmlParse( population ) > <cfreturn retPopXML /> </cffunction> </cfcomponent>
Ray Camden's excellent toXML ColdFusion Component is included in the sample files, along with a small sample of dummy data.
 
Here is the SQL used to create the table for the demo.
 
CREATE TABLE [demo].[tbl_pop]( [cKey] [char](10) COLLATE Latin1_General_CI_AS NULL, [census1864] [numeric](18, 0) NULL, [census1890] [numeric](18, 0) NULL, [census1911] [numeric](18, 0) NULL, [census1920] [numeric](18, 0) NULL, [census1930] [numeric](18, 0) NULL, [census1940] [numeric](18, 0) NULL, [census1950] [numeric](18, 0) NULL, [census1960] [numeric](18, 0) NULL, [census1970] [numeric](18, 0) NULL, [census1981] [numeric](18, 0) NULL, [census1991] [numeric](18, 0) NULL, [census2001] [numeric](18, 0) NULL ) ON [PRIMARY]
Next, we need to define a Web Service Object that points to your service with the appropriate handlers for the results and the fault. Note that the mx:operation name maps to the service name and the countryid parameter is bound to a variable that has been created in the script block:
 
Service:
 
//this in the script block [Bindable] private var _currentCountryId:String <mx:WebService id="popRequest" wsdl="wsdl file" showBusyCursor="true" result="handleResult(event)" fault="Alert.show(event.fault.message)"> <mx:operation name="getData"> <mx:request> <countryid>{_currentCountryId}</countryid> </mx:request> </mx:operation> </mx:WebService>
Handlers:
 
import mx.rpc.events.*; private function handleResult(ev:ResultEvent):void { Alert.show("data captured") Alert.show(ev.result.toString()) }
All that remains now is to invoke the service and pass it the single parameter. The parameter is going to be the map key and we already have a function that is called at the appropriate time:
 
/* called whenever a country clicked */ private function handleMapItemClick(ev:MapEvent):void { try { //set the URL of the HTML component to the concatenated URL using // the stem of the Cia Factbook and the two character code pane.location=_stem + ev.mapFeature.key.toLowerCase() + ".html" //save the key that you clicked on into the bound variable _currentCountryId which updated the web service call _currentCountryId=ev.mapFeature.key //invoke the web service popRequest.getData.send() } catch (e:Error) { // if the user clicks on the sea for example Alert.show ("please click on a country") } }
If you put a breakpoint into the handleResult service, you'll see that XML is returned containing the population data. This is the data we'll use for the last element in the dashboard: a 3D chart.
 

 
Adding a 3D chart

The 3D charts that come with the ILog Elixir components are very dynamic, easy to implement and simple to integrate with existing and new applications.
 
To add one, select the columnChart3D from the ILog Elixir folder in the component palette and drop it on the Stage. Because the 3D charts are based on the ChartBase class, they contain most of the same functionality as the standard Adobe charts, but they are supercharged!
 
First we'll set up the data provider. Return to the handler function for the custom result event from the web service and place that information into an XmlListCollection which will be used as the data provider for the chart. This XmlListCollection must be declared and created at the top of the script block of your application and given a [Bindable] metatag as shown below:
 
import mx.collections.XMLListCollection //the data provider for the 3D chart [Bindable] Private var _chartDP:XMLListCollection = new XMLListCollection(); private function handleResult(ev:ResultEvent):void { Alert.show("data captured") Alert.show(ev.result.toString()) //add each entry to the chart dp //in this demo we are assuming that only a single item at a time is //returned but if multiple selection on the map //is enabled you could update the web service and then just loop //through the results _chartDP.addItem(ev.result[0].row) }
Next, apply the data provider to the chart and specify the axes. The horizontal axis will be a category listing which will show the countries as they are added to the data provider:
 
<ilog:ColumnChart3D id="popChart" paddingLeft="5" paddingRight="5" showDataTips="true" dataProvider="{_chartDP}" x="8" y="414"> <ilog:horizontalAxis> <!-- CKEY is a field returned from the service--> <mx:CategoryAxis categoryField="CKEY"/> </ilog:horizontalAxis>
Finally set the columnSeries3D. This is similar to the columnseries in the standard Flex Charts. You need to set the xField value (corresponding to the category axis above) and the yField (this is simply enumerating each of the columns returned from the web service, but of course they could be calculated dynamically in the web service handler):
 
<ilog:ColumnChart3D id="popChart" paddingLeft="5" paddingRight="5" showDataTips="true" dataProvider="{chartDP}" x="8" y="414"> <ilog:horizontalAxis> <!-- CKEY is a field returned from the service--> <mx:CategoryAxis categoryField="CKEY"/> </ilog:horizontalAxis> <ilog:series > <!-- add a column series for each of the fields that you want represented --> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1864" displayName="census 1864"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1890" displayName="census 1890"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1911" displayName="census 1911"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1920" displayName="census 1920"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1930" displayName="census 1930"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1940" displayName="census 1940"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1950" displayName="census 1950"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1960" displayName="census 1960"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1970" displayName="census 1970"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1981" displayName="census 1981"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS1991" displayName="census 1991"/> <ilog:ColumnSeries3D xField="CKEY" yField="CENSUS2001" displayName="census 2001"/> </ilog:series> </ilog:ColumnChart3D>
By binding the fields of the data to the yFields of the columnSeries3D, the chart automatically updates as the underlying data is populated.
 
Because the 3D charts extends the ChartBase, you still have access to all the neat things that Flex standard charting provides you with—such as the effects:
 
<mx:SeriesInterpolate duration="1000" id="myAnimation"/> <ilog:ColumnSeries3D showDataEffect="myAnimation" xField="CKEY" yField="CENSUS2001" displayName="census 2001"/>
You can also take advantage of the new 3D specific properties and adjust the chart as desired:
 
<ilog:ColumnChart3D id="popChart" paddingLeft="5" paddingRight="5" showDataTips="true" rotationAngle="20" elevationAngle="12" dataProvider="{_chartDP}" x="8" y="414">

 
Where to go from here

In this sample project, both the map and the charts provide a wealth of user interaction for quick and easy dashboard customization and rich client feedback. I'm preparing a follow-up article that takes this project a step further. In that article I'll explore how to create controls to animate both the map and the charts to allow drill downs and navigation. Stay tuned.
 
In the meantime, take some time to become familiar with the ILog Elixir components. Each component comes with an excellent and heavily commented working sample that you can take apart and analyze to see how these remarkable effects are achieved. I also highly recommend reading Elixir's blog.