function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
dturkeldturkel 

Creating a "Stacked" Bar Chart, using Google Visualizations codeshare library

All,

 

I need to make a bar chart that has data grouped within each series, so that each "bar" in the chart is combination of multiple data points.  I am able to do this using Google Charts, but providing the information with a clean legend is problematic, so I am investigating the use of Google Visualizations.

 

An example of the type of the Google Chart, without legends for the subdata making up the bars:

 

I stumbled upon the codeshare library for Google Visualizations (http://wiki.developerforce.com/index.php/Google_Visualizations).

 

I was wondering if anyone had used the ColumnChart component and related code to create this type of Chart?  It looks possible, just wanted a quick sanity check before I invest in a lot of time on it.

 

 

Thanks,

 

David

Google ChartsGoogle Charts

I am facing the similar issue. I would like to use Google column chart to show stack bar.

 

Each stack bar has different number of sub items.

 

Any help would be appreciated.

yoganand19yoganand19

Hi

 

I need to create a stacked bar chart. Could you please help with the required code???

dturkeldturkel

 

I have multiple sets of code listed below.  The first is my initial attempt to do the charting, without using the GoogleViz (and related components).  The second set of code is an example (and supporting utility Color class) for using the GoogleViz component -- though it would need to be modified/genericed as needed.

 

 

The code below is a very "manual" way of creating the chart w/o using the GoogleViz libraries.

 

public class GoogleChartsUtilities {
	
	public static final String BASE_URL = 'https://chart.googleapis.com/chart?';
	public static final String CHART_TYPE_PARAM = 'cht=';
	public static final String CHART_TITLE_PARAM = 'chtt=';
	public static final String CHART_GRID_PARAM = 'chg=';
	public static final String CHART_COLORS_PARAM = 'chco=';
	public static final String CHART_SIZE_PARAM = 'chs=';
	public static final String CHART_AXIS_PARAM = 'chxt=';
	public static final String CHART_AXIS_RANGE_PARAM = 'chxr=';
	public static final String CHART_BAR_STYLE_PARAM = 'chbh=';
	public static final String CHART_AXIS_LABEL_PARAM = 'chxl=';
	public static final String CHART_LEGEND_PARAM = 'chdl=';
	public static final String CHART_LEGEND_POSITION_PARAM = 'chdlp=';
	public static final String CHART_DATA_PARAM = 'chd=t:';
	public static final String CHART_SCALE_PARAM = 'chds=';
	
	private static final Map<Integer,String> HEX_MAP = new Map<Integer,String>();	
	static {
		HEX_MAP.put(0,'0');
		HEX_MAP.put(1,'1');
		HEX_MAP.put(2,'2');
		HEX_MAP.put(3,'3');
		HEX_MAP.put(4,'4');
		HEX_MAP.put(5,'5');
		HEX_MAP.put(6,'6');
		HEX_MAP.put(7,'7');
		HEX_MAP.put(8,'8');
		HEX_MAP.put(9,'9');
		HEX_MAP.put(10,'A');
		HEX_MAP.put(11,'B');
		HEX_MAP.put(12,'C');
		HEX_MAP.put(13,'D');
		HEX_MAP.put(14,'E');
		HEX_MAP.put(15,'F');
	}
	
	/**
		Create a Matrix-style Bar Chart, each bar represents a Grouping (Major), and segments(Minor) within that group are stacked
		Requires a List of AggregateResult objects representing each Major/Minor Grouping of data.
		e.g.:
		Video - Basic Video
		Video - Extended Video
		Video - Extremem Video
		HSD - Slow
		HSD - Fast
		
		Returns a Map of Chart Parameters to their values.  This allows the caller to override and add parameters
		as desired.
	*/
	public static Map<String,String> constructMatrixBarGoogleChart(
		List<AggregateResult> aggregateResultList, 
		String majorGroupFieldName, 
		String minorGroupFieldName, 
		String summaryFieldName,
		String chartTitle,
		String chartSizeDimensions)
	{
		//The final value to be returned
		Map<String,String> chartParameterToValues = new Map<String,String>();
		
		//Working Variables to Hold constructed values
		String CHART_COLORS_VAL = '';
		String CHART_AXIS_RANGE_VAL = '';
		String CHART_AXIS_LABEL_VAL = '';
		String CHART_LEGEND_VAL = '';
		String CHART_DATA_VAL = '';
		String CHART_SCALE_VAL = '';
		
		//Map in defaulted parameters
		chartParameterToValues.put(CHART_TYPE_PARAM, 'bvs');
		chartParameterToValues.put(CHART_TITLE_PARAM, chartTitle);
		chartParameterToValues.put(CHART_SIZE_PARAM, chartSizeDimensions);
		chartParameterToValues.put(CHART_BAR_STYLE_PARAM, 'r,1,1');
		chartParameterToValues.put(CHART_LEGEND_POSITION_PARAM, 'b|a');
		
		String majorBarGrouping = '';
		Map<String,List<MinorSubgroupingSummaryVO>> majorToMinortoSummary = new Map<String,List<MinorSubgroupingSummaryVO>>(); 
		
		Integer countDistinctMajorGroups = 0;
		Integer maxMajorMinorEntrieSize = 0;
		Integer currentRguServiceSize = 0;
		
		Double largestMajorTotalAmount = 0;
		Double currentMajorTotalAmount = 0;
		Map<String,Double> majorGroupToTotalSummaryAmount = new Map<String,Double>();
		
		for (AggregateResult ar : aggregateResultList){
			String majorGroupValue = (String)ar.get(majorGroupFieldName);
			String subgroupName = (String)ar.get(minorGroupFieldName);
			String summaryValue = String.valueOf(ar.get(summaryFieldName));
			
			MinorSubgroupingSummaryVO currentMinorGroupSummaryVO = new MinorSubgroupingSummaryVO();
			currentMinorGroupSummaryVO.subgroupName = subgroupName;
			currentMinorGroupSummaryVO.valueSummary = summaryValue;
			
			System.debug('majorGroupValue:'+ majorGroupValue + ' - subgroupName:'+ subgroupName + ' - summaryValue:'+ summaryValue);
			//establish/update counts
			if(majorGroupValue != majorBarGrouping){
				
				//majorGroupValue changed so increase unique majorGroupValue's AND indicate new Service Size
				countDistinctMajorGroups++;
				if(currentRguServiceSize > maxMajorMinorEntrieSize){
					maxMajorMinorEntrieSize = currentRguServiceSize;
				}
				if(currentMajorTotalAmount > largestMajorTotalAmount){
					largestMajorTotalAmount = currentMajorTotalAmount;
				}
			
				currentRguServiceSize = 1; //reset the currentRguServiceSize
				currentMajorTotalAmount = Double.valueOf(summaryValue); //
				majorBarGrouping = majorGroupValue; //set the new majorGroupValue as the previous for the next round
				
			}
			else{
				//continue by adding another amount to current RguServiceSize
				currentRguServiceSize++;
				currentMajorTotalAmount += Double.valueOf(summaryValue);
			}
			
			//update data
			if(majorToMinortoSummary.containsKey(majorGroupValue)){
				System.debug('Found entry for key: ' + majorGroupValue);
				List<MinorSubgroupingSummaryVO> myMinorSubgroupSummaryVoList = majorToMinortoSummary.get(majorGroupValue);
				myMinorSubgroupSummaryVoList.add(currentMinorGroupSummaryVO);
				
			}
			else{
				//initialize
				System.debug('New majorGroupValue: ' + majorGroupValue);
				List<MinorSubgroupingSummaryVO> myMinorSubgroupSummaryVoList = new List<MinorSubgroupingSummaryVO>();
				myMinorSubgroupSummaryVoList.add(currentMinorGroupSummaryVO);
				majorToMinortoSummary.put(majorGroupValue, myMinorSubgroupSummaryVoList);
			}
			
		}
		
		System.debug('countDistinctMajorGroups:' + countDistinctMajorGroups + ' - maxMajorMinorEntrieSize:' + maxMajorMinorEntrieSize);
		System.debug('majorToMinortoSummary: ' + majorToMinortoSummary);
		

		//for now use the GET URL style.  Eventually this needs to be converted as a post, and probably passed along to Javascript?
		//set CHART_DATA_VAL, CHART_LEGEND_VAL, and CHART_COLORS_VAL
		//loop through all of the Major entries, based on the maxMajorMinorEntrieSize
		Set<String> majorKeys = majorToMinortoSummary.keySet();
		
		//develop colors for each bar/series
		Map<String, List<String>> mapMajorGroupToColorSeries = new Map<String, List<String>>();
		for(String key : majorKeys){
			List<String> colorsForBar = GoogleChartsUtilities.getDataSetColors(maxMajorMinorEntrieSize);
			mapMajorGroupToColorSeries.put(key, colorsForBar);
		}
		
		for(Integer loopCount = 0; loopCount < maxMajorMinorEntrieSize; loopCount++){
			System.debug('On Loop Count: ' + loopCount);
			boolean isFirst = true;
			for(String key : majorKeys){
				if(isFirst){
					isFirst = false;
				}
				else{
					CHART_DATA_VAL += ',';
					CHART_LEGEND_VAL += ',';
					CHART_COLORS_VAL += '|';
				}
				System.debug('Working with key: ' + key);
				List<MinorSubgroupingSummaryVO> myMinorSubgroupSummaryVoList = majorToMinortoSummary.get(key);
				System.debug('Located list, size=' + myMinorSubgroupSummaryVoList.size());
				if(loopCount < myMinorSubgroupSummaryVoList.size()){
					MinorSubgroupingSummaryVO srsVo = myMinorSubgroupSummaryVoList.get(loopCount);
					CHART_DATA_VAL += srsVo.valueSummary;
					CHART_LEGEND_VAL += srsVo.subgroupName;
					CHART_COLORS_VAL += mapMajorGroupToColorSeries.get(key).get(loopCount);
				}
				else{
					//There shouldn't be data available for this specific item in the series
					CHART_DATA_VAL +=  '0';
					CHART_LEGEND_VAL += 'N/A';
					CHART_COLORS_VAL += mapMajorGroupToColorSeries.get(key).get(loopCount);
				}
			}
			if(loopCount+1 < maxMajorMinorEntrieSize){
				CHART_DATA_VAL +=  '|';
				CHART_LEGEND_VAL += '|';
				CHART_COLORS_VAL += ',';
			}
		}

		chartParameterToValues.put(CHART_DATA_PARAM, CHART_DATA_VAL);
		chartParameterToValues.put(CHART_COLORS_PARAM, CHART_COLORS_VAL);
		
		//Define the Chart Scale Parameters
		chartParameterToValues.put(CHART_SCALE_PARAM, '1,' + largestMajorTotalAmount);
		
		//Define Axis Param/Range/Scale/Labels
		chartParameterToValues.put(CHART_AXIS_PARAM, 'x,y');
		chartParameterToValues.put(CHART_AXIS_RANGE_PARAM, '1,0,' + largestMajorTotalAmount);
		
		
		String xAxisLabelFormatString = '0:|';
		//re-use majorKeys above as the X-Axis Labels
		for(String key : majorKeys){
			xAxisLabelFormatString += key += '|';
		}
		chartParameterToValues.put(CHART_AXIS_LABEL_PARAM, xAxisLabelFormatString);
		
		System.debug('Generated Map: ' + chartParameterToValues);
		return chartParameterToValues;	
	}
	
	public static String constructGoogleChartUrl(Map<String,String> chartParamValueMap, boolean useRandomChartServer){
		String url = '';
		//Determine the Chart Server Base URL
		if(useRandomChartServer){
			Integer serverNumber = Math.Round(Math.Random() * 9);
			url += 'https://' + serverNumber + '.chart.googleapis.com/chart?';
		}
		else{
			url += BASE_URL;
		}
		
		boolean isFirst = true;
		for(String key : chartParamValueMap.keySet()){
			if(isFirst){
				//no '&'
				url += key + chartParamValueMap.get(key);
				isFirst=false;
			}
			else{
				url += '&' + key + chartParamValueMap.get(key);
			}
		}
		return url;
	}
	
	/**
		Produces a series of colors that should be used for a data set (bar) in our chart
	*/
	public static List<String> getDataSetColors(Integer maxNumberOfDataPoints){
		//First we need to determine a strong base color based on two or the RGB parameters
		//Which two?
		Integer firstRgbIndex = Math.round(2 * Math.random());
		Integer secondRgbIndex = firstRgbIndex;
		Integer thirdRgbIndex = 2;
		while(firstRgbIndex == secondRgbIndex){
			secondRgbIndex = Math.round(2 * Math.random());
		}
		//determine the third, variable RGB color index
		if(firstRgbIndex != 0 && secondRgbIndex != 0){
			thirdRgbIndex = 0;
		}
		else if(firstRgbIndex !=1 && secondRgbIndex != 1){
			thirdRgbIndex = 1;
		}	
		
		Integer firstRgbColor = 0;
		Integer secondRgbColor = 0;
		String firstRgbColorHex = '';
		String secondRgbColorHex = '';
		//Second, generate the actual strong colors for the rbg indexes
		//Going to use min/max values of 128-255 (88-FF)
		firstRgbColor = Math.round(128 + (127 * Math.random()));
		secondRgbColor = Math.round(128 + (127 * Math.random()));
		//Convert these to Hexadecimal Values
		firstRgbColorHex = convertIntegerToHexadecimal(firstRgbColor);
		secondRgbColorHex = convertIntegerToHexadecimal(secondRgbColor);
		
		Map<Integer, String> rgbToBaseColorIndexMap = new Map<Integer, String>();
		rgbToBaseColorIndexMap.put(firstRgbIndex, firstRgbColorHex);
		rgbToBaseColorIndexMap.put(secondRgbIndex, secondRgbColorHex);
		
		
		//Now, generate a series of *visible* hex values for the third datapoint (0-200?)
		Integer interval = Math.round(200/maxNumberOfDataPoints - 1); //minus 1 because the first value will always be 0
		
		List<String> thirdHexColorList = new List<String>();
		thirdHexColorList.add('00');//00 will always be the staring value, and variants are added
		for(Integer intervalCount = 1; intervalCount<maxNumberOfDataPoints;intervalCount++){		
			thirdHexColorList.add(convertIntegerToHexadecimal(interval * intervalCount));
		}
		System.debug('thirdHexColorList: ' + thirdHexColorList);
		
		
		List<String> colorsForBar = new List<String>();
		//Add variant colors
		for(Integer colorListCount=0; colorListCount < thirdHexColorList.size(); colorListCount++){
			String finalColor = '000000';
			//first hex
			if(firstRgbIndex == 0){
				finalColor=firstRgbColorHex;
			}
			else if(secondRgbIndex == 0){
				finalColor=secondRgbColorHex;
			}
			else{
				finalColor=thirdHexColorList.get(colorListCount);
			}
			//second hex
			if(firstRgbIndex == 1){
				finalColor+=firstRgbColorHex;
			}
			else if(secondRgbIndex == 1){
				finalColor+=secondRgbColorHex;
			}
			else{
				finalColor+=thirdHexColorList.get(colorListCount);
			}
			//third hex
			if(firstRgbIndex == 2){
				finalColor+=firstRgbColorHex;
			}
			else if(secondRgbIndex == 2){
				finalColor+=secondRgbColorHex;
			}
			else{
				finalColor+=thirdHexColorList.get(colorListCount);
			}
			colorsForBar.add(finalColor);
		}
		
		return colorsForBar;
	}
	
	//Expected to be a value between 0 and 255
	public static String convertIntegerToHexadecimal(Integer value){
		Integer modFirstCharacterNumber = Math.mod(value, 16);
		Integer firstCharacterNumber = (value - modFirstCharacterNumber)/16;
		String firstCharacterHexcode = HEX_MAP.get(firstCharacterNumber);
		String secondCharacterHexcode = HEX_MAP.get(modFirstCharacterNumber);
		String hexValue = '';
		hexValue = firstCharacterHexcode + secondCharacterHexcode;
		
		return hexValue;
	}
	
	/** Inner container class for services' revenue values */
	class MinorSubgroupingSummaryVO{
		public String subgroupName {get;set;}
		public String valueSummary {get;set;}
	}
}
dturkeldturkel

 

The next set of code assumes you are using/have the GoogleViz and JSONObject classes.  It refactors some of the "color" methods from the above code into a separate utility class.  The code that actually creates the chart is in a VF controller -- it really should be genericized more, but this gives you the idea.  I believe we had to make a modification to the ColumnChart component provided by Google as well, so I've included that last.  This was built some time ago, so I'm not sure how compaitble it is with anything that is out there.

 

 

public class NewChartsTestController {

    public NewChartsTestController() {
        buildData();
    }

    public NewChartsTestController(ApexPages.StandardController controller) {}

    private String colorString = '';
    private String salesDataString;

    public String getColorString() {return colorString;}
    public String getSalesDataString() {return salesDataString;}
    
    public void addColors(Integer rowSize, Boolean endingComma) {
        List<String> colorList = ColorUtilities.getDataSetColors(rowSize);
        for (String color:colorList) {
            colorString += '\'#' + color + '\',';
        }
        if (!endingComma) {
            colorString = colorString.substring(0, colorString.length()-1);
        }
    }
    
    
    public void buildData(){
       
        List<AggregateResult> aggregateResultList = 
                           [SELECT RGU__c, Service_Name__c, 
                                    SUM(Projected_Revenue__c) sum_projected_revenue
                             FROM Rep_Object_One__c 
                             WHERE Contract_Type__c = 'Bulk Only' 
                                    AND Region__c =: 'Northeast'
                             GROUP BY RGU__c, Service_Name__c
                             ORDER BY RGU__c, Service_Name__c];
        
      // Create the list of columns based on the service names from the result list
      GoogleViz gv = new GoogleViz();
        List<GoogleViz.col> columns = new List<GoogleViz.col>();
        columns.add (new GoogleViz.Col('col0','RGU','string'));
        Integer columnIndex = 1;
        for ( AggregateResult ar : aggregateResultList ) {
            columns.add(new GoogleViz.Col('col'+columnIndex++,(String)ar.get('Service_Name__c'),'number'));
        }
        gv.cols = columns;
        
        String lastRGU = 'XX';
        GoogleViz.row r;
        Integer resultCount = 0;
        Integer itemsInCurrentRow = 0;

        for ( AggregateResult ar : aggregateResultList ) {
           resultCount++;
           String rgu = (String)ar.get('RGU__c');
           String serviceName = (String)ar.get('Service_Name__c');
           Double revenue = (Double)ar.get('sum_projected_revenue');
           if (rgu != lastRGU) {
               if (r != null) {
                   // End old row, fill remainder with zero cells
                   for (Integer i=resultCount+1;i<14;i++) {
                       r.cells.add(new GoogleViz.cell(0));
                   }
                   gv.addRow(r); 
                   addColors(itemsInCurrentRow, true);
                   r = new GoogleViz.row();
                   itemsInCurrentRow = 0;
                   r.cells.add ( new GoogleViz.cell(rgu) );
                   // New row, pad with zero cells up to result count
                   for (Integer i=1;i<resultCount;i++) {
                       r.cells.add(new GoogleViz.cell(0));
                   }
               } else {
                   r = new GoogleViz.row();
                   itemsInCurrentRow = 0;
                   r.cells.add ( new GoogleViz.cell(rgu) );
               }
               lastRGU = rgu;
           }
           r.cells.add(new GoogleViz.cell(revenue));
           itemsInCurrentRow++;
         }            
        gv.addRow(r);
        addColors(itemsInCurrentRow, false);
        salesDataString = gv.toJsonString();   
     } 
}

 

ColorUtilties class:

public class ColorUtilities {
    
    public static List<String> getDataSetColors(Integer maxNumberOfDataPoints){
        //First we need to determine a strong base color based on two or the RGB parameters
        //Which two?
        Integer firstRgbIndex = Math.round(2 * Math.random());
        Integer secondRgbIndex = firstRgbIndex;
        Integer thirdRgbIndex = 2;
        while(firstRgbIndex == secondRgbIndex){
            secondRgbIndex = Math.round(2 * Math.random());
        }
        //determine the third, variable RGB color index
        if(firstRgbIndex != 0 && secondRgbIndex != 0){
            thirdRgbIndex = 0;
        }
        else if(firstRgbIndex !=1 && secondRgbIndex != 1){
            thirdRgbIndex = 1;
        }   
        
        Integer firstRgbColor = 0;
        Integer secondRgbColor = 0;
        String firstRgbColorHex = '';
        String secondRgbColorHex = '';
        //Second, generate the actual strong colors for the rbg indexes
        //Going to use min/max values of 128-255 (88-FF)
        firstRgbColor = Math.round(128 + (127 * Math.random()));
        secondRgbColor = Math.round(128 + (127 * Math.random()));
        //Convert these to Hexadecimal Values
        firstRgbColorHex = convertIntegerToHexadecimal(firstRgbColor);
        secondRgbColorHex = convertIntegerToHexadecimal(secondRgbColor);
        
        Map<Integer, String> rgbToBaseColorIndexMap = new Map<Integer, String>();
        rgbToBaseColorIndexMap.put(firstRgbIndex, firstRgbColorHex);
        rgbToBaseColorIndexMap.put(secondRgbIndex, secondRgbColorHex);
        
        
        //Now, generate a series of *visible* hex values for the third datapoint (0-200?)
        Integer interval = Math.round(200/maxNumberOfDataPoints - 1); //minus 1 because the first value will always be 0
        
        List<String> thirdHexColorList = new List<String>();
        thirdHexColorList.add('00');//00 will always be the staring value, and variants are added
        for(Integer intervalCount = 1; intervalCount<maxNumberOfDataPoints;intervalCount++){        
            thirdHexColorList.add(convertIntegerToHexadecimal(interval * intervalCount));
        }
        System.debug('thirdHexColorList: ' + thirdHexColorList);
        
        
        List<String> colorsForBar = new List<String>();
        //Add variant colors
        for(Integer colorListCount=0; colorListCount < thirdHexColorList.size(); colorListCount++){
            String finalColor = '000000';
            //first hex
            if(firstRgbIndex == 0){
                finalColor=firstRgbColorHex;
            }
            else if(secondRgbIndex == 0){
                finalColor=secondRgbColorHex;
            }
            else{
                finalColor=thirdHexColorList.get(colorListCount);
            }
            //second hex
            if(firstRgbIndex == 1){
                finalColor+=firstRgbColorHex;
            }
            else if(secondRgbIndex == 1){
                finalColor+=secondRgbColorHex;
            }
            else{
                finalColor+=thirdHexColorList.get(colorListCount);
            }
            //third hex
            if(firstRgbIndex == 2){
                finalColor+=firstRgbColorHex;
            }
            else if(secondRgbIndex == 2){
                finalColor+=secondRgbColorHex;
            }
            else{
                finalColor+=thirdHexColorList.get(colorListCount);
            }
            colorsForBar.add(finalColor);
        }
        
        return colorsForBar;
    }
    
    //Expected to be a value between 0 and 255
    public static String convertIntegerToHexadecimal(Integer value){
        Integer modFirstCharacterNumber = Math.mod(value, 16);
        Integer firstCharacterNumber = (value - modFirstCharacterNumber)/16;
        String firstCharacterHexcode = HEX_MAP.get(firstCharacterNumber);
        String secondCharacterHexcode = HEX_MAP.get(modFirstCharacterNumber);
        String hexValue = '';
        hexValue = firstCharacterHexcode + secondCharacterHexcode;
        
        return hexValue;
    }

    private static final Map<Integer,String> HEX_MAP = new Map<Integer,String>();   
    static {
        HEX_MAP.put(0,'0');
        HEX_MAP.put(1,'1');
        HEX_MAP.put(2,'2');
        HEX_MAP.put(3,'3');
        HEX_MAP.put(4,'4');
        HEX_MAP.put(5,'5');
        HEX_MAP.put(6,'6');
        HEX_MAP.put(7,'7');
        HEX_MAP.put(8,'8');
        HEX_MAP.put(9,'9');
        HEX_MAP.put(10,'A');
        HEX_MAP.put(11,'B');
        HEX_MAP.put(12,'C');
        HEX_MAP.put(13,'D');
        HEX_MAP.put(14,'E');
        HEX_MAP.put(15,'F');
    }
}
dturkeldturkel

 

 ColumnChart component:

<apex:component >
    <apex:attribute name="jsondata" description="This is the chart data" type="string" required="true" />
    <apex:attribute name="title" description="This is the chart title" type="string" required="true" />
    <apex:attribute name="height" description="This is the chart height" type="string" default="250" />
    <apex:attribute name="width" description="This is the chart width" type="string" default="400" />
    <apex:attribute name="legend" description="This is the legend location" type="string" default="right" />
    <apex:attribute name="colors" description="This is the list of colors for the columns." type="string[]"/>
    <apex:attribute name="borderColor" description="This is the color of the border around chart elements." type="string" default="white" />
    <apex:attribute name="focusBorderColor" description="This is the color of the border around chart elements that are in focus." type="string" default="gray" />
    <apex:attribute name="is3D" description="This deteremines whether or not the chart is 3D."  type="boolean" default="false"/>
    <!-- sample selectListenerBody attribute: selectListenerBody="window.location.href = '/apex/DrilldownPage';"  -->
    <apex:attribute name="selectListenerBody" description="The JavaScript function definition that will be executed when a Select event occurs. (Do not use the double-quote character in the body.)" type="string" default=""/>
    
    <apex:outputPanel id="chart_div" layout="block" style="width:{!width}px;height={!height}px" >
        <script type="text/javascript" src="http://www.google.com/jsapi"></script>
        <script type="text/javascript">
             google.load("visualization", "1.1", {packages: ["columnchart"]});
             google.setOnLoadCallback(drawLineChart); 
             var data = new Object();
             function drawLineChart() {
                data = new google.visualization.DataTable( eval( '({!jsondata})' ) );
            
                var chart = new google.visualization.ColumnChart( document.getElementById('{!$Component.chart_div}') );
                chart.draw(data, 
                           {width: {!width}, 
                            height: {!height}, 
                            legend: '{!legend}', 
                            title: '{!title}', 
                            isStacked: 'true',
                            is3D : {!is3D},
                            colors : [{!colors}],
                            borderColor : '{!borderColor}',
                            focusBorderColor : '{!focusBorderColor}'
                           });  

                var listenerBody = "{!selectListenerBody}";
                if(listenerBody != ''){
                    var selectListener = new Function("e", listenerBody);
                    google.visualization.events.addListener(chart, 'select', selectListener);
                }                          
            }
        </script>
    </apex:outputPanel>
</apex:component>