Friday, May 20, 2011

Getting Started. Part 2

In the previous post of the Getting started with amCharts Bundle v.2, we talked about different approaches to get the best browser support for our graphs. In this second part, we will develop some examples covering every type of strategy and we will try to expose some best case scenarios for each of them.

Four different implementation strategies were explained in our first article:

  • Flash only: charts will be generated using Flash technology; if the browser lacks support for it, it won't display anything.
  • JavaScript only: charts will be generated using a JavaScript; if the browser does not support SVG or VML or if it has JavaScript disabled, it won't display anything either.
  • Flash with fallback to JavaScript: this is a workaround for the first case, when the browser doesn't support Flash. Thanks to swfobject, included in amCharts bundle, we can detect if Flash is supported and if the appropriate version is available, so when these needs are not accomplished we can provide a JavaScript fallback.
  • JavaScript with fallback to Flash: is also possible to provide a Flash fallback to the second case; in this case the AmCharts.recommended() function is used to guess the best rendering option for the current browser, so we may provide a Flash fallback when JavaScript is not our best shot.

Let's see how to get these functionalities within our code, covering the implementation of the different strategies exposed above, trying to display the following chart:

Image

In the following examples we will just focus on how we can implement the different fallback strategies, leaving for future posts the explanation of how to configure different charts' settings.

JavaScript with fallback to Flash

Our first example will be a JavaScript only chart, to which we will add a Flash fallback. Here we can find the needed code to display a JavaScript only chart:

HTML+ Javascript

<html>
    <head>
        <title>amCharts: Javascript only strategy</title>
       
        <script src="../amcharts/javascript/amcharts.js" type="text/javascript"></script>
        
       
        <script type="text/javascript">           
            AmCharts.ready(function() {
                loadJavascriptChart("chartdiv", "data.txt");
            });
           
            // this method creates a javascript chart ploting data from external file
            function loadJavascriptChart(elementId, file) {
                var chart = new AmCharts.AmPieChart();
                // first we load the external data file
                var data = loadFile(file);                             
                // then we set a data provider to the chart
                chart.dataProvider = createDataProvider(data);
               
                chart.titleField = "country";
                chart.valueField = "litres";
               
                chart.innerRadius = 30;
                chart.sequencedAnimation = true;
                chart.labelText = "[[title]]: [[value]]";
                chart.write(elementId);            
            }  
           
            // method which parses csv data
            function createDataProvider(data){
                var rows = data.split("\n");
                // create array which will hold our data:
                dataProvider = [];
               
                // loop through all rows
                for (var i = 0; i < rows.length; i++){
                    // this line helps to skip empty rows
                    if (rows[i]) {                   
                        // our columns are separated by a semicolon
                        var column = rows[i].split(";"); 
                       
                        // column is array now
                        var country = column[0];
                        var litres = column[1];
                                               
                        // create object which contains all these items:
                        var dataObject = {"country":country, "litres":litres};
                        // add object to dataProvider array
                        dataProvider.push(dataObject);
                    }
                }
                return dataProvider;     
            }  
           
            // method which loads external data
            function loadFile(file) {
                if (window.XMLHttpRequest) {
                    // IE7+, Firefox, Chrome, Opera, Safari
                    var request = new XMLHttpRequest();
                }
                else {
                    // code for IE6, IE5
                    var request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                // load
                request.open('GET', file, false);
                request.send();
               
                // now lets load data into a new flash chart
                var data = request.responseText;
                //replace UNIX new line
                data = data.replace (/\r\n/g, "\n");
                //replace MAC new lines
                data = data.replace (/\r/g, "\n");
               
                return data;
            }
        </script>
    </head>
   
    <body>
   
        <div id="chartdiv" style="width:600px; height:400px; background-color:#FFFFFF"></div>
   
    </body>
</html>

We have defined a function that loads an external CSV file containing the data to plot and once loaded, we need to parse it and create dataProvider, that will be use to feed data into our chart. Finally, we defined a function that setups and displays the Javascript chart. Then when the HTML AmCharts.ready() event is fired, we just need to call that function, passing as parameter the ID of the DOM element that will contain our chart and the name of the external file containing the data.

Now we will add some enhancements to our base code so we can provide a Flash fallback, for the occasion when Javascript is not the best option. Firs of all, we have to modify the previous AmCharts.ready() event to look something like this:

AmCharts.ready(function() {
     if (AmCharts.recommended() == "js"){
          loadJavascriptChart("chartdiv", "data.txt");
     } else {
          loadFlashChart("chartdiv", "data.txt");   
     }               
});

Now it relies on AmCharts.recommended() function to decide which option is the best, and depending on its response, a Javascript or a Flash chart will be used. The loadFlashChart() function just used in the previous block of code, looks like this:

// this method creates a flash chart ploting data from external file
function loadFlashChart(elementId, file) {
     // we set up the flash chart params
     var flashVars = {
          path: "../amcharts/flash/",
          chart_data: loadFile(file),
          chart_settings:
               "<settings>" +
                    "<data_type>csv</data_type>" +
                    "<legend><enabled>0</enabled></legend>" +
                    "<pie>" +
                         "<inner_radius>30</inner_radius>" +
                    "</pie>" +
                    "<animation>" +
                         "<start_time>1</start_time>" +
                         "<pull_out_time>1</pull_out_time>" +
                    "</animation>" +
                    "<data_labels>" +
                         "<show>{title}: {value}</show>" +
                         "<max_width>140</max_width>" +
                    "</data_labels>" +
               "</settings>"
     };
     var params = {
          bgcolor:"#FFFFFF"
     };
     // finally we insert a new flash object in DOM
     swfobject.embedSWF(
          "../amcharts/flash/ampie.swf",
          elementId,
          "600", "400",
          "8.0.0",
          "../amcharts/flash/expressInstall.swf",
          flashVars,
          params
     );
}  

Basically what we achieve with the previous code is to convert the associative data array to a CSV string, and then we setup the flash chart to look exactly as the JavaScript one.

Finally, we need to add to our HTML a couple of references in order to get the Flash version chart working. The full HTML of the JavaScript with Flash fallback example is shown as follows:

HTML + Javascript

<html>
    <head>
        <title>amCharts: Javascript with Flash fallback strategy</title>
       
        <script src="../amcharts/flash/swfobject.js" type="text/javascript"></script>
       
        <script src="../amcharts/javascript/amcharts.js" type="text/javascript"></script>
        <script src="../amcharts/javascript/amfallback.js" type="text/javascript"></script>
       
        <script type="text/javascript">               
            AmCharts.ready(function() {
                if (AmCharts.recommended() == "js"){
                    loadJavascriptChart("chartdiv", "data.txt");
                } else {
                    loadFlashChart("chartdiv", "data.txt");   
                }               
            });
           
            // this method creates a javascript chart ploting data from external file
            function loadJavascriptChart(elementId, file) {
                var chart = new AmCharts.AmPieChart();
                    // first we load the external data file
                var data = loadFile(file);                             
                // then we set a data provider to the chart
                chart.dataProvider = createDataProvider(data);
               
                chart.titleField = "country";
                chart.valueField = "litres";
               
                chart.innerRadius = 30;
                chart.sequencedAnimation = true;
                chart.labelText = "[[title]]: [[value]]";
                chart.write(elementId);            
            }           
            // this method creates a flash chart ploting data from external file
            function loadFlashChart(elementId, file) {
                    // we set up the flash chart params
                var flashVars = {
                    path: "../amcharts/flash/",
                    chart_data: loadFile(file),
                    chart_settings:
                        "<settings>" +
                            "<data_type>csv</data_type>" +
                            "<legend><enabled>0</enabled></legend>" +
                            "<pie>" +
                                "<inner_radius>30</inner_radius>" +
                            "</pie>" +
                            "<animation>" +
                                "<start_time>1</start_time>" +
                                "<pull_out_time>1</pull_out_time>" +
                            "</animation>" +
                            "<data_labels>" +
                                "<show>{title}: {value}</show>" +
                                "<max_width>140</max_width>" +
                            "</data_labels>" +
                        "</settings>"
                };
                var params = {
                    bgcolor:"#FFFFFF"
                };
                    // finally we insert a new flash object in DOM
                swfobject.embedSWF(
                    "../amcharts/flash/ampie.swf",
                    elementId,
                    "600", "400",
                    "8.0.0",
                    "../amcharts/flash/expressInstall.swf",
                    flashVars,
                    params
                );
            }           
            // method which parses csv data
            function createDataProvider(data){
                var rows = data.split("\n");
                // create array which will hold our data:
                dataProvider = [];
               
                // loop through all rows
                for (var i = 0; i < rows.length; i++){
                    // this line helps to skip empty rows
                    if (rows[i]) {                   
                        // our columns are separated by a semicolon
                        var column = rows[i].split(";"); 
                       
                        // column is array now
                        var country = column[0];
                        var litres = column[1];
                                               
                        // create object which contains all these items:
                        var dataObject = {"country":country, "litres":litres};
                        // add object to dataProvider array
                        dataProvider.push(dataObject);
                    }
                }
                return dataProvider;     
            }
           
            // method which loads external data
            function loadFile(file) {
                if (window.XMLHttpRequest) {
                    // IE7+, Firefox, Chrome, Opera, Safari
                    var request = new XMLHttpRequest();
                }
                else {
                    // code for IE6, IE5
                    var request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                // load
                request.open('GET', file, false);
                request.send();
               
                // now lets load data into a new flash chart
                var data = request.responseText;
                //replace UNIX new line
                data = data.replace (/\r\n/g, "\n");
                //replace MAC new lines
                data = data.replace (/\r/g, "\n");
               
                return data;
            }           
        </script>
    </head>
   
    <body>
   
        <div id="chartdiv" style="width:600px; height:400px; background-color:#FFFFFF"></div>
   
    </body>
</html>

When can we expect the Flash fallback chart to be displayed? It completely depends on the result of AmCharts.recommended() function. At the current version of amCharts library, this function tries to detect if the browser supports SVG, returning "js" in that case; if it doesn't, then it returns "flash" whenever Flash 8 or greater is supported. The purpose of this function is expected to remain as is in future versions of amCharts, so it's quite safe to rely on it, abstracting our code from this kind of "tricks" used to detect browser features. 

Flash with fallback to Javascript

Our last example will be a Flash only chart, to which we will add a JavaScript fallback. As before, we will start with the following code:

HTML+ Javascript

<html>
    <head>
        <title>amCharts: Flash only strategy</title>
        <script src="../amcharts/flash/swfobject.js" type="text/javascript"></script>
        <script type="text/javascript">
            AmCharts.ready(function() {
                loadFlashChart("chartdiv", "data.txt");
            });
           
            // this method creates a flash chart ploting data from external file
            function loadFlashChart(elementId, file) {
                // we set up the flash chart parameters
                var flashVars = {
                    path: "../amcharts/flash/",
                    chart_data: loadFile(file),
                    chart_settings:
                        "<settings>" +
                            "<data_type>csv</data_type>" +
                            "<legend><enabled>0</enabled></legend>" +
                            "<pie>" +
                                "<inner_radius>30</inner_radius>" +
                            "</pie>" +
                            "<animation>" +
                                "<start_time>1</start_time>" +
                                "<pull_out_time>1</pull_out_time>" +
                            "</animation>" +
                            "<data_labels>" +
                                "<show>{title}: {value}</show>" +
                                "<max_width>140</max_width>" +
                            "</data_labels>" +
                        "</settings>"
                };
                var params = {
                    bgcolor:"#FFFFFF"
                };
                // finally we insert a new flash object in DOM
                swfobject.embedSWF(
                    "../amcharts/flash/ampie.swf",
                    elementId,
                    "600", "400",
                    "8.0.0",
                    "../amcharts/flash/expressInstall.swf",
                    flashVars,
                    params
                );
            }           
           
            // method which loads external data
            function loadFile(file) {
                if (window.XMLHttpRequest) {
                    // IE7+, Firefox, Chrome, Opera, Safari
                    var request = new XMLHttpRequest();
                }
                else {
                    // code for IE6, IE5
                    var request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                // load
                request.open('GET', file, false);
                request.send();
                // now lets load data into a new flash chart
                var data = request.responseText;
                //replace UNIX new line
                data = data.replace (/\r\n/g, "\n");
                //replace MAC new lines
                data = data.replace (/\r/g, "\n");
                return data;
            }
        </script>
    </head>
    <body>
        <div id="chartdiv"></div>
    </body>
</html>

In order to add the JavaScript fallback, we have to modify the AmCharts.ready() event:

AmCharts.ready(function() {
     if (swfobject.hasFlashPlayerVersion("8")){
          loadFlashChart("chartdiv");
     } else {
          loadJavascriptChartFromFlashConfig("chartdiv");
     }
    
});

Now the decision of which version to display depends on whether the browser has support or not for Flash version 8 or greater, which can be found out by using swfobject library. With the new implementation of the AmCharts.ready() event, the function loadJavascriptChartFromFlashConfig()  will be invoked whenever Flash is not present or not well supported.

The new loadJavascriptChartFromFlashConfig() function creates a JavaScript fallback chart via amCharts API, reusing configuration and data from a previously created Flash chart. Its content is shown as follows:

function loadJavascriptChartFromFlashConfig(elementId) {
     var amFallback = new AmCharts.AmFallback();
     amFallback.chartSettings = flashVars.chart_settings;
     amFallback.chartData = flashVars.chart_data;
     amFallback.type = "pie";
     amFallback.write(elementId);
} 

Finally, our complete HTML listing for this last example should look like this:

HTML+ Javascript

<html>
    <head>
        <title>amCharts: Flash with Javascript fallback strategy</title>
       
        <script src="../amcharts/flash/swfobject.js" type="text/javascript"></script>
       
        <script src="../amcharts/javascript/amcharts.js" type="text/javascript"></script>
        <script src="../amcharts/javascript/amfallback.js" type="text/javascript"></script>
       
        <script type="text/javascript">               
            AmChart.ready(function() {
                if (swfobject.hasFlashPlayerVersion("8")){
                    loadFlashChart("chartdiv");
                } else {
                    loadJavascriptChartFromFlashConfig("chartdiv");
                }
            });
           
            var flashVars = {
                path: "../amcharts/flash/",
                chart_data: loadFile("data.txt"),
                chart_settings:
                    "<settings>" +
                        "<data_type>csv</data_type>" +
                        "<legend><enabled>0</enabled></legend>" +
                        "<pie>" +
                            "<inner_radius>30</inner_radius>" +
                        "</pie>" +
                        "<animation>" +
                            "<start_time>1</start_time>" +
                            "<pull_out_time>1</pull_out_time>" +
                        "</animation>" +
                        "<data_labels>" +
                            "<show>{title}: {value}</show>" +
                            "<max_width>140</max_width>" +
                        "</data_labels>" +
                    "</settings>"
            };
           
            function loadJavascriptChartFromFlashConfig(elementId) {
                var amFallback = new AmCharts.AmFallback();
                amFallback.chartSettings = flashVars.chart_settings;               
                amFallback.chartData = flashVars.chart_data;
                amFallback.type = "pie";
                amFallback.write(elementId);
            }
           
            function loadFlashChart(elementId) {
                var params = {
                    bgcolor:"#FFFFFF"
                };
                swfobject.embedSWF(
                    "../amcharts/flash/ampie.swf",
                    elementId,
                    "600", "400",
                    "8.0.0",
                    "../amcharts/flash/expressInstall.swf",
                    flashVars,
                    params
                );
            }           
           
            // method which loads external data
            function loadFile(file) {
                if (window.XMLHttpRequest) {
                    // IE7+, Firefox, Chrome, Opera, Safari
                    var request = new XMLHttpRequest();
                }
                else {
                    // code for IE6, IE5
                    var request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                // load
                request.open('GET', file, false);
                request.send();
               
                // now lets load data into a new flash chart
                var data = request.responseText;
                //replace UNIX new line
                data = data.replace (/\r\n/g, "\n");
                //replace MAC new lines
                data = data.replace (/\r/g, "\n");
               
                return data;
            }           
        </script>
    </head>
   
    <body>
   
        <div id="chartdiv" style="width:600px; height:400px; background-color:#FFFFFF"></div>
   
    </body>
</html>