1,707 articles and 12,972 comments as of Sunday, September 26th, 2010

Friday, May 7, 2010

SharePoint: Interactive Charts Using Google Visualization API

Guest Author: Alexander Bautz
SharePoint JavaScripts

5/24/10 Note: The code for the file “ChartUsingGoogleVisualizationAPI.js” is updated. Modified the array of fields to use for chart label and value columns. The two now uses the same array: ‘Number’, ‘Currency’, ‘Text’, ‘Calculated’, ‘Boolean’ ,’User’ ,’DateTime’ ,’Choice’ ,’Lookup’. Added support for Lookup column as “Chart label column”. The code for the file “ChartUsingGoogleVisualizationAPI.js” is updated and added some examples at the bottom of the article. Thanks, Alexander!

This is a solution for creating charts directly from SharePoint lists using Google Visualization API. The solution is a CEWP-solution and requiring no other than a few script references. All code and data are processed and rendered in the browser. No data is sent to any server.

The solution features “Sum”, “Count” and “Average”, and can pull data from any column type, including calculated columns. The chart config is stored in a separate “chart config list”, but all the configuration is done in the GUI seen in the bottom picture.

You can pull data from any existing list view (even from personal views – for personal use), or using a custom CAML-query. If you use a custom query, you have the ability to filter the data source using a user profile property pulled from the user profile of the logged in user.

You may use a regular expression to separate out the part of the profile property to use:


A few pictures to look at while you wait for the code:

Multiple charts setup in the same CEWP


The same page in “Edit page mode”:


The code

As always we start like this:
Create a document library to hold your scripts (or a folder on the root created in SharePoint Designer). In this example I have made a document library with a relative URL of “/test/English/Javascript” (a sub site named “test” with a sub site named “English” with a document library named “Javascript”):


The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.4.2.min. If you use another version, please update the reference in the code.

The scripts “interaction.js” and “stringBuffer.js” is created by Erucy and published on CodePlex.

Create a list to hold the configuration with these fields:

  • Title (the standard title field already in the list)
  • ChartConfig (Multiple lines of plain text)
  • LabelCol (Single line of text)
  • NumCols (Multiple lines of plain text)
  • UseCustomCAML (Yes/No check box)
  • CustomQuery (Multiple lines of plain text)
  • ViewGuid (Single line of text)
  • ListGuid (Single line of text)
  • ChartHeight (Single line of text)
  • ChartWidth (Single line of text)
  • ChartType (Single line of text)
  • GetFilterFromProfile (Yes/No check box)
  • UserProfileProperty (Single line of text)
  • RegexProfileProperty (Single line of text)

Note: These are FieldInternalNames and must be exactly like the above fields.

The GUID for this configuration list is used in the CEWP code below. Read here how to get the GUID for a list.

Add this code to a CEWP and place it in the page where you want the chart to appear:

<!-- Chart goes in this container -->
<div id="DemoChart1"></div>

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript" src="/test/English/Javascript/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/interaction.js"></script>
<script type="text/javascript" src="/test/English/Javascript/stringBuffer.js"></script>
<script type="text/javascript" src="/test/English/Javascript/ChartUsingGoogleVisualizationAPI.js"></script>
<script type="text/javascript">
/*****************************************************
			Set list Guids and parameters
*****************************************************/
// List name or Guid of the config list. This list must reside in the same site as the charts are to be displayed
chartConfigListGuid = 'E7A71324-043F-49A0-95EF-1E3E51DD8A85';
// The "People and Groups" list - must be set to support filtering by user profile data.
userListGuid = "{570D772F-0EAB-45A8-8C54-9CCD4EC6A0AF}";
// Modify if the site collection is on a managed path
userListBaseUrl = '';
// Each chart must be represented by a container with a unique id. This container  must be present in the page
arrOfChartContainers = ['DemoChart1'];

/*****************************************************
			Init charts
*****************************************************/
// Load the visualizations from Google
google.load("visualization","1",{packages:["columnchart","barchart","areachart","linechart","piechart","OrgChart"]});
google.setOnLoadCallback(onloadInitChart);
// Call the script when the visualization packages are loaded
function onloadInitChart(){
	call_drawChart(arrOfChartContainers);
}
</script>

The list item in the configuration list is automatically created when you call the script using a “chartID” not already found in the configuration list:


A “chartID” can be “reused” if you want the same chart to appear in multiple pages. The chart configuration list is not suppoosed to be “hand edited”, all configuration are done trough the UI in “Edit page mode”. There i however nothing that stops you from hand editing the configuration if you like.

When adding a new chartID, you get an empty chart:


Edit page to access the configuration:


The code for the file “ChartUsingGoogleVisualizationAPI.js”:

/* Charting for SharePoint using Google Visualization API
 * ---------------------------------------------
 * Created by Alexander Bautz
 * [email protected]
 * http://sharepointjavascript.wordpress.com
 * Copyright (c) 2009-2010 Alexander Bautz (Licensed under the MIT X11 License)
 * v1.0
 * LastMod: 24.05.2010
 * LastChange: Modified the array of fields to use for chart label and value columns. The two now uses the same array:
			   'Number', 'Currency', 'Text', 'Calculated', 'Boolean' ,'User' ,'DateTime' ,'Choice' ,'Lookup'
 * ---------------------------------------------
 * Include reference to:
 *  jquery - http://jquery.com
 *  interaction.js - http://spjslib.codeplex.com/
 *  stringBuffer.js - http://spjslib.codeplex.com/
 * 	http://www.google.com/jsapi
 *	ChartUsingGoogleVisualizationAPI.js (this file)
 * ---------------------------------------------
*/

function call_drawChart(arrOfChartContainerIDs){
	$.each(arrOfChartContainerIDs,function(i,chartContainerID){
		init_drawChart(chartContainerID);
	});
}

function init_drawChart(cId){
// Check if container is defined
if($("#"+cId).length==0){
	alert("The container with id "+cId+", is not defined!");
	return;
}
$("#"+cId).before("<div id='"+cId+"_chartConfig' style='padding:10px;border:1px silver solid;background-color:#F5F5DC;display:none'></div>");
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
var query = "<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>"+cId+"</Value></Eq></Where>";
chartConfig = queryItems(chartConfigListGuid,query,
	['ID',
	'Title',
	'ChartConfig',
	'LabelCol',
	'NumCols',
	'UseCustomCAML',
	'CustomQuery',
	'GetFilterFromProfile',
	'UserProfileProperty',
	'RegexProfileProperty',
	'ListGuid',
	'ViewGuid',
	'ChartHeight',
	'ChartWidth',
	'ChartType'],1);
	if(chartConfig.count==0){
		addChartId = addItem(chartConfigListGuid,{'Title':cId,'ChartHeight':'250','ChartWidth':'500','ChartConfig':'title:Add chart title here'});
		if(!addChartId.success){
			alert("An error occured while creating the configuration container:\n"+addChartId.errorText);
		}else{
			var myChartConfig = {
				myChartId:cId,
				chartConfigID:addChartId.id,
				configRaw:'title:Add chart title here',
				useCustomCAML:false,
				useUserProfileProperty:false,
				userProfileProperty:'',
				regexProfileProperty:'',
				customCAML:'',
				listGuid:'',
				viewGuid:'',
				labelCol:'',
				numCols:'',
				chartHeight:'200',
				chartWidth:'450',
				chartType:'ColumnChart'}
		}
	}else{
		thisChartConfig = chartConfig.items[0];
		var myChartConfig = {
			myChartId:cId,
			chartConfigID:thisChartConfig['ID'],
			configRaw:(thisChartConfig['ChartConfig']!=null)?thisChartConfig['ChartConfig']:'',
			useCustomCAML:(thisChartConfig['UseCustomCAML']==1)?true:false,
			useUserProfileProperty:(thisChartConfig['GetFilterFromProfile']==1)?true:false,
			userProfileProperty:(thisChartConfig['UserProfileProperty']!=null)?thisChartConfig['UserProfileProperty']:'',
			regexProfileProperty:(thisChartConfig['RegexProfileProperty']!=null)?thisChartConfig['RegexProfileProperty']:'',
			customCAML:(thisChartConfig['CustomQuery']!=null)?thisChartConfig['CustomQuery']:'',
			listGuid:(thisChartConfig['ListGuid']!=null)?thisChartConfig['ListGuid']:'',
			viewGuid:(thisChartConfig['ViewGuid']!=null)?thisChartConfig['ViewGuid']:'',
			labelCol:(thisChartConfig['LabelCol']!=null)?thisChartConfig['LabelCol']:'',
			numCols:(thisChartConfig['NumCols']!=null)?thisChartConfig['NumCols']:'',
			chartHeight:(thisChartConfig['ChartHeight']!=null)?thisChartConfig['ChartHeight']:'200',
			chartWidth:(thisChartConfig['ChartWidth']!=null)?thisChartConfig['ChartWidth']:'450',
			chartType:(thisChartConfig['ChartType']!=null)?thisChartConfig['ChartType']:'ColumnChart'}
	}
	// Code inactive in "edit page mode"
	if($(".ms-WPAddButton").length>0){
		editChartConfig(myChartConfig);
	}else{
		// build chart option object
		chartOptions = {};
		if(arrConfig!=''){
			var arrConfig = myChartConfig.configRaw.split(';');
			$.each(arrConfig,function(i,optRaw){
				var split = optRaw.split(':');
				if(split.length==2){
					var param = split[0];
					var val = split[1];
				}else{
					var param = optRaw.substring(0,optRaw.indexOf(':'));
					var val = optRaw.substring(optRaw.indexOf(':')+1);
				}

				if(param=='colors'){
					var colorArr = [];
					if(val.indexOf('color')>-1 && val.indexOf('darker')>-1){
						var colorArrRaw = val.match(/\{color\:.*?\}/g);
						$.each(colorArrRaw,function(i,colorRaw){
							var colorSplit = colorRaw.replace(/{|}|"|'/g,'').split(',');
							var color = colorSplit[0].split(':')[1];
							var darker = colorSplit[1].split(':')[1];
							obj = {color:$.trim(color),darker:$.trim(darker)};
							colorArr.push(obj);
						});
					}else{
						var colorArrRaw = val.replace(/\[|\]|"|'/g,'').split(',');
						$.each(colorArrRaw,function(i,color){
							colorArr.push(color);
						});
					}
					val = colorArr;
				}
				chartOptions[param]=val;
			});
		}
		// Width and height
		chartOptions.height=myChartConfig.chartHeight;
		chartOptions.width=myChartConfig.chartWidth;

		// labelCol
		if(myChartConfig.labelCol!=''){
			myChartConfig.labelCol = myChartConfig.labelCol.split(':')[0];
		}
		// NumCol
		numColNameLabelAndType = [];
		if(myChartConfig.numCols!=''){
			var arrNumFields = myChartConfig.numCols.split(';');
			$.each(arrNumFields,function(i,fieldOpt){
				if(fieldOpt.length==0)return;
				var full = fieldOpt.split(',');
					numFieldObj = {};
					$.each(full,function(i,paramRaw){
						var split = paramRaw.split(':');
						var param = split[0];
						var val = split[1];
						numFieldObj[param]=val;

					});
				numColNameLabelAndType.push(numFieldObj);
			});
		}
		// Call chartBuilsing function
		drawChart(myChartConfig,numColNameLabelAndType,chartOptions);
	}
}

/*****************************************************
					Get views
*****************************************************/
function getViewsForThisList(obj,lGuid,vGuid){
	var onLoad = true;
	if(typeof(obj)=='object'){
		onLoad = false;
		var listGuid = obj.find('option:selected').val();
		var chartID = obj.attr('chartID');
	}else{
		var listGuid = lGuid;
	}
	if(listGuid!=''){
		var viewColl = customGetViewCollection(listGuid);
		var options = "<option value=''>&lt;Select view&gt;</option>";
		$.each(viewColl.views,function(){
		var personalView = '';
		var personalViewTooltip = '';
			if($(this).attr('Personal')=='TRUE'){
				var personalView = " (Personal view)";
				var personalViewTooltip = "If you create a chart from a personal view, the chart will only be available for you. Other users accessing the chart will receive a \"No Data\" message.";
			}
			if($(this).attr('Name')==vGuid){
				selected = "selected='selected'";
			}else{
				selected = '';
			}
			options+="<option title='"+personalViewTooltip+"' value='"+$(this).attr('Name')+"' "+selected+">"+$(this).attr('DisplayName')+personalView+"</option>";
		});
		// Load eller select
		if(onLoad){
			return options;
		}else{
			$("#"+chartID+"_viewGuid").html(options);
			//customGetListFields(listGuid,false);
			fieldsOnloadOrOnchange('',listGuid,chartID);
		}
	}else{
		// Load or select
		if(onLoad){
			alert("ListGuid not defined");
			return '';
		}else{
			$("#"+chartID+"_viewGuid").html('');
			$("#"+chartID+"_labelFieldsDiv").html('');
			$("#"+chartID+"_numFieldsDiv").parents('td:first').find('select').each(function(){
				$(this).remove();
			});
			$("#"+chartID+"_numFieldsDiv").html('');
			$("#"+chartID+"_cloneNumSelectLink").hide();
		}
	}
}

function customCloneFieldSelector(chartID){
	var td = $("#"+chartID+"_numColTd");
	var clone = td.find('select:last').parent().clone();
	clone.find('option:first').attr('selected',true);
	td.append("<div>"+clone.html()+"</div>");
}

/*****************************************************
					Save config
*****************************************************/
function saveChartConfig(obj,chartId,chartConfigId){
chartConfigData = [];
var configDiv = obj.parents("div[id='"+chartId+"_chartConfig']");
	// Current options
	configDiv.find("input[fin='ChartConfig']").each(function(){
		chartConfigData.push($(this).attr('optionName')+":"+$(this).val());
	});
	// New options
	configDiv.find("div.newChartConfigDiv").each(function(){
		var newOptionName = $(this).find('input:first').val();
		var newOptionVal = 	$(this).find('input:last').val();
		if(newOptionName!=''&&newOptionVal!=''){
			chartConfigData.push(newOptionName+":"+newOptionVal);
		}
	});

	useCustomCAML = (configDiv.find("input[id='"+chartId+"_UseCustomCAML']").attr('checked')==true)?'1':'0';
	useUserProfileProperty = (configDiv.find("input[id='"+chartId+"_GetFilterFromUserProfile']").attr('checked')==true)?'1':'0';
	userProfileProperty = configDiv.find("select[id='"+chartId+"_selectUserprofileProperty'] option:selected").val();
	regexProfileProperty= configDiv.find("input[id='"+chartId+"_RegexProfileProperty']").val();

	customQueryData = $.trim(configDiv.find("textarea[id='"+chartId+"_customQuery']").val());
	listGuidVal = $.trim(configDiv.find("select[id='"+chartId+"_listGuid'] option:selected").val());
	viewGuidVal = $.trim(configDiv.find("select[id='"+chartId+"_viewGuid'] option:selected").val());

	var labelSelect = $("#"+chartId+"_labelFieldsDiv").find('select option:selected');
	labelCol = labelSelect.val()+":"+labelSelect.text();

	numCols = '';
	$("#"+chartId+"_numColTd").find('span').each(function(){
		if($(this).attr('fin')!=undefined){
			numCols += "label:"+$(this).attr('value')+",fin:"+$(this).attr('fin')+",action:"+$(this).attr('action')+",prefix:"+$(this).attr('prefix')+",fieldType:"+$(this).attr('fieldType')+";";
		}
	});

	$("#"+chartId+"_numColTd").find('select.numFieldSelect').each(function(){
		var thisOpt = $(this).find('option:selected');
		if(thisOpt.val()!=''){
			var fieldAction = $(this).next().find('option:selected').val();
			var prefix = $(this).next().next().attr('checked');
			numCols += "label:"+thisOpt.text()+",fin:"+thisOpt.val()+",action:"+fieldAction+",prefix:"+prefix+",fieldType:"+thisOpt.attr('fieldType')+";";
		}
	});
	chartHeight = $.trim(configDiv.find("input[id='"+chartId+"_ChartHeight']").val());
	chartWidth = $.trim(configDiv.find("input[id='"+chartId+"_ChartWidth']").val());
	chartType = configDiv.find("select[id='"+chartId+"_selectChartType'] option:selected").val();
	wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
	res = updateItem(chartConfigListGuid,chartConfigId,
		{'ChartConfig':chartConfigData.join(';'),
		'UseCustomCAML':useCustomCAML,
		'GetFilterFromProfile':useUserProfileProperty,
		'UserProfileProperty':userProfileProperty,
		'RegexProfileProperty':regexProfileProperty,
		'CustomQuery':customQueryData,
		'ListGuid':listGuidVal,
		'ViewGuid':viewGuidVal,
		'LabelCol':labelCol,
		'NumCols':numCols,
		'ChartHeight':chartHeight,
		'ChartWidth':chartWidth,
		'ChartType':chartType});

	if(!res.success){
		alert("Update chart config error:\n"+res.errorText);
	}else{
		if(confirm("Saved OK\n\nRefresh page?"))location.href=location.href;
	}
}

/*****************************************************
				Test regular expression
*****************************************************/
function testRegExp(id){
	var testRegExp = $("#"+id+"_RegexProfileProperty").val();
	var profileProperty = $("#"+id+"_selectUserprofileProperty").val();
	var up = getUserInfo();
	if(up[profileProperty]!=''){
		try
		{
		var regexResult = up[profileProperty].match(testRegExp);
		msgBuffer = ["The full text in the user property field \""+profileProperty+"\" for the user \""+up.Title+"\" is:\n"];
		msgBuffer.push(up[profileProperty]);
		msgBuffer.push("\n\nThe full RegExp match is this:\n"+regexResult);
		msgBuffer.push("\n\nThe part that will be used as a filter is this:\n"+regexResult[1]);
		var msg = msgBuffer.join('');
		}catch(err){
			var msg = err.description;
		}
	}else{
		var msg = "The property field \""+profileProperty+"\" is empty!";
	}
	alert(msg);
}

/*****************************************************
				Chart config options link
*****************************************************/
function setLinkToConfigurationOptions(obj,chartType,chartId){
var link = [];
var onLoad = true;
	if(typeof(obj)=='object'){
		var chartId = obj.attr('chartID');
		var selectedChartOption = obj.find('option:selected');
		var chartType = selectedChartOption.val();
		var chartTypeFriendly = selectedChartOption.text();
	}else{
		var selectedChartOption = $("#"+chartId+"_selectChartType").find('option:selected');
		var chartType = selectedChartOption.val();
		var chartTypeFriendly = selectedChartOption.text();
	}
	link.push("<a title='Opens in new window' href='http://code.google.com/intl/en-EN/apis/visualization/documentation/gallery/");
	link.push(chartType.toLowerCase()+".html#Configuration_Options' target='_blank'>Instructions for "+chartTypeFriendly+"</a>");
	link = link.join('');
	// Write to placeholder
	$("#"+chartId+"_chartOptionsLink").html("<div style='font-size:10px;font-weight:normal;display:inline'>"+link+"</div>");
}

/*****************************************************
				Edit chart config
*****************************************************/
function editChartConfig(config){
var editOptLeftColArr = [];
var editOptRightColArr = [];
var editOptBottomArr = [];
var editOptTopColArr = [];
var editOptTopLeftColArr = [];
var editOptTopRightColArr = [];
var arrOfChartTypes = ['BarChart|Bar Chart','ColumnChart|Column Chart','AreaChart|Area Chart','LineChart|Line Chart',"PieChart|Pie Chart","OrgChart|Org Chart"];
var strChartTypeOptions = [];
	// Chart types
	$.each(arrOfChartTypes,function(i,typeRaw){
	var split = typeRaw.split('|');
		var cType = split[0];
		var cTypeFriendly = split[1];
		if(config.chartType==cType){
			strChartTypeOptions.push("<option value='"+cType+"' selected>"+cTypeFriendly+"</option>");
		}else{
			strChartTypeOptions.push("<option value='"+cType+"'>"+cTypeFriendly+"</option>");
		}
	});

	strChartTypeOptions= strChartTypeOptions.join('');
	// ChartType
		var strArr = [];
			strArr.push("<tr>");
			strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Chart type</label><br />");
			strArr.push("<select id='"+config.myChartId+"_selectChartType' chartID='"+config.myChartId+"' onchange='javascript:setLinkToConfigurationOptions($(this))'>");
			strArr.push(strChartTypeOptions);
			strArr.push("</selec>");
			strArr.push("</td></tr>");

// ListGuid
views = "<option value='' selected>Select list first</option>";

var listColl = customGetListCollection();
var listOptionsStrBuffer = ["<option value=''>&lt;Select list&gt;</option>"];
$.each(listColl.lists,function(){
	if($(this).attr('Name')==config.listGuid){
		views = getViewsForThisList('',config.listGuid,config.viewGuid);
		selected = "selected='selected'";
	}else{
		selected = '';
	}
	listOptionsStrBuffer.push("<option value='"+$(this).attr('Name')+"' "+selected+">"+$(this).attr('Title')+"</option>");
});

	listSelect = "<select id='"+config.myChartId+"_listGuid' chartID='"+config.myChartId+"' onchange='javascript:getViewsForThisList($(this),\""+config.listGuid+"\",\""+config.viewGuid+"\")'>"+listOptionsStrBuffer.join('')+"</select>";

		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Select list</label><br />");
		strArr.push(listSelect);
		strArr.push("</td></tr>");		

// ViewGuid
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Select view or use custom CAML</label><br />");
		strArr.push("</td></tr><tr><td colspan='2' style='padding:0 5 0 5'><select id='"+config.myChartId+"_viewGuid' chartID='"+config.myChartId+"'>");
		strArr.push(views);
		strArr.push("</select>");
		strArr.push("</td></tr>");
// Width and height
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Chart height</label><br />");
		strArr.push("<input style='width:100px' id='"+config.myChartId+"_ChartHeight' value='"+config.chartHeight+"' />");
		strArr.push("</td></tr>");
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Chart width</label><br />");
		strArr.push("<input style='width:100px' id='"+config.myChartId+"_ChartWidth' value='"+config.chartWidth+"' />");
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptTopLeftColArr.push(str);	

// Fields
	var strArr = [];
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Chart label column</label><br />");
		strArr.push("<div id='"+config.myChartId+"_labelFieldsDiv'></div>");
		strArr.push("</td></tr>");
		strArr.push("<tr><td colspan='2' style='padding:3 5 0 5'><label style='font-weight:bold'>Chart value columns</label></td></tr>");
		strArr.push("<tr><td colspan='2' id='"+config.myChartId+"_numColTd' style='padding:0 5 0 5'><div id='"+config.myChartId+"_numFieldsDiv'></div></td></tr>");
		strArr.push("<tr><td colspan='2' style='padding:0 5 0 5'><a id='"+config.myChartId+"_cloneNumSelectLink' chartID='"+config.myChartId+"' style='display:none' href='javascript:customCloneFieldSelector(\""+config.myChartId+"\")'>Add new</a></td></tr>");
		strArr.push("<tr><td colspan='2' style='padding:10 5 0 5'><div title='Click to read about column types' style='cursor:pointer;font-weight:bold' onclick='$(this).next().toggle()'>About column types</div>");
		strArr.push("<div style='display:none;width:350px'>");
		strArr.push("<div style='padding:3px;border:1px silver solid'>Columns containing numbers can be summed. The column can contain text and number mixed. ");
		strArr.push("A Regular expression extracts the number from the text.<br /><br />");
		strArr.push("However notice that only the first occurance of a int/float is used.<br /><br />");
		strArr.push("Boolean columns can be counted or summed (Yes=1, No=0).<br /><br />");
		strArr.push("All columns can be counted (not empty=1, empty=0)</div></div></td></tr>")
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptTopRightColArr.push(str);	

// Options
	if(config.configRaw!=''){
	var arrConfigData = config.configRaw.split(';');
		$.each(arrConfigData,function(i,rawOption){
			split = rawOption.split(':');
			if(split.length==2){
				var label = split[0];
				var val = split[1].replace(/'/g,'"');
			}else{
				var label = rawOption.substring(0,rawOption.indexOf(':'));
				var val = rawOption.substring(rawOption.indexOf(':')+1).replace(/'/g,'"');
			}
			var strArr = [];
				strArr.push("<tr>");
				strArr.push("<td style='padding:0 5 0 5'>"+label+"</td>");
				strArr.push("<td style='padding:0 5 0 5'><input style='width:150px' ");
				strArr.push("fin='ChartConfig' optionName='"+label+"' id='"+config.myChartId+"_"+label+"' type='Text' value='"+val+"' />");
				strArr.push("<a title='Remove current option' style='padding-left:5px' href='javascript:' onclick='addOrRemove($(this),false)'>X</a>");
				strArr.push("</td></tr>");
				str = strArr.join('');
			editOptLeftColArr.push(str);
		});
	}

// Add new option
	var strArr = [];
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style=''>");
		strArr.push("<a style='padding-left:5px' href='javascript:' onclick='addOrRemove($(this),true)'>Add new option</a>");
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptLeftColArr.push(str);

	// CAML
	var customCAMLchecked = '';
	var customCAMLAreaDisplay = 'none'
	if(config.useCustomCAML){
		customCAMLchecked = 'checked';
		customCAMLAreaDisplay = 'block';
	}
	var filterFromUPchecked = '';
	var filterFromUPdisplay = 'none';
	if(config.useUserProfileProperty){
		filterFromUPchecked = 'checked';
		var filterFromUPdisplay = 'block';
	}

	var strArr = [];
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='padding:0 5 0 5'>");
		strArr.push("<input type='checkbox' id='"+config.myChartId+"_UseCustomCAML' "+customCAMLchecked+" onclick='javascript:$(\"#"+config.myChartId+"_tableUseCustomCAML\").toggle()'><label for='"+config.myChartId+"_UseCustomCAML'>Use custom CAML (overrides selected view)</label>");
		strArr.push("</td></tr>");
		strArr.push("<tr><td id='"+config.myChartId+"_tableUseCustomCAML' style='display:"+customCAMLAreaDisplay+"'>");
		strArr.push("<table><tr><td>");
		strArr.push("<input type='checkbox' id='"+config.myChartId+"_GetFilterFromUserProfile' "+filterFromUPchecked+" onclick='javascript:$(\"#"+config.myChartId+"_userProfilePropertyDescription\").toggle()'><label for='"+config.myChartId+"_GetFilterFromUserProfile'>Get filter value from this profile property:&nbsp;</label>");
		strArr.push("<select id='"+config.myChartId+"_selectUserprofileProperty' chartID='"+config.myChartId+"'>");
		var arrOfUserProfileProperties = ['ID','Name','Title','EMail','Department','JobTitle','SipAddress'];
		$.each(arrOfUserProfileProperties,function(i,prop){
			var propSelected = '';
			if(prop==config.userProfileProperty){
				propSelected = 'selected';
			}
			strArr.push("<option value='"+prop+"' "+propSelected+">"+prop+"</option>");
		});
		strArr.push("</select>");
		strArr.push("</td></tr>");
		strArr.push("<tr><td colspan='2' style='padding:0 5 0 5'>");
		strArr.push("<div id='"+config.myChartId+"_userProfilePropertyDescription' style='width:390px;display:"+filterFromUPdisplay+"'>");
		strArr.push("<div style='color:red'>");
		strArr.push("To use the user profile property as a filter, you must insert {} as a placeholder where the user profile property is to be inserted.<br /><br />");
		strArr.push("If you use a regular expression to identify the value, it is the first backreference that is used.</div><br />");
		strArr.push("<label>Optional RegEx to match property:&nbsp;</label><a href='javascript:testRegExp(\""+config.myChartId+"\")'>Test regular expression</a><br />");
		strArr.push("<input id='"+config.myChartId+"_RegexProfileProperty' type='Text' value='"+config.regexProfileProperty+"' style='width:100%'></div>");
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptRightColArr.push(str);

	escapedCAML = config.customCAML.replace(/</g,'&lt;').replace(/>/g,'&gt;');
	var strArr = [];
		strArr.push("<tr>");
		strArr.push("<td style='padding:0 5 0 5'><label>Custom CAML-query</label><br />");
		strArr.push("<textarea style='width:400px;height:150px' id='"+config.myChartId+"_customQuery'>"+escapedCAML+"</textarea>");
		strArr.push("</td></tr></table>");
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptRightColArr.push(str);
// Save button
	var strArr = [];
		strArr.push("<tr>");
		strArr.push("<td colspan='2' style='text-align:right'>");
		strArr.push("<input onclick='javascript:saveChartConfig($(this),\""+config.myChartId+"\",\""+config.chartConfigID+"\");' type='button' value='Save' />");
		strArr.push("</td></tr>");
		str = strArr.join('');
	editOptBottomArr.push(str);

// wrap up
	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%' cellpadding='0' cellspacing='2'>");
		strArr.push(editOptTopColArr.join(''));
		strArr.push("</table>");
	wrapTop = strArr.join('');

	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%;' cellpadding='2' cellspacing='0'>");
		strArr.push(editOptTopLeftColArr.join(''));
		strArr.push("</table>");
	wrapTopLeft = strArr.join('');

	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%' cellpadding='2' cellspacing='0'>");
		strArr.push(editOptTopRightColArr.join(''));
		strArr.push("</table>");
	wrapTopRight = strArr.join('');

	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%;' cellpadding='2' cellspacing='0'>");
		strArr.push(editOptLeftColArr.join(''));
		strArr.push("</table>");
	wrapBottomLeft = strArr.join('');

	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%' cellpadding='2' cellspacing='0'>");
		strArr.push(editOptRightColArr.join(''));
		strArr.push("</table>");
	wrapBottomRight = strArr.join('');

	var strArr = [];
		strArr.push("<table style='background-color:#ffffff;width:100%' cellpadding='2' cellspacing='0'>");
		strArr.push(editOptBottomArr.join(''));
		strArr.push("</table>");
	wrapBottom = strArr.join('');

	var wrap = [];
		wrap.push("<table cellpadding='0' cellspacing='0'>");
		wrap.push("<tr><td valign='top' colspan='2' style='padding:5px;font-size:14px;font-weight:bold;background-color:#F5F5DC'>Chart configuration for "+config.myChartId+"</td>");
		wrap.push("<tr><td valign='top' colspan='2' style='border-left:1px silver solid;border-top:1px silver solid;border-right:1px silver solid'>"+wrapTop+"</td></tr>");
		wrap.push("<tr><td valign='top' style='border-left:1px silver solid;border-bottom:1px silver solid;padding:3px'>"+wrapTopLeft+"</td>")
		wrap.push("<td valign='top' style='border-right:1px silver solid;border-bottom:1px silver solid;padding:3px'>"+wrapTopRight+"</td></tr>");
		wrap.push("<tr><td valign='top' style='padding:5px;font-size:14px;font-weight:bold;background-color:#F5F5DC'>Options&nbsp;<span id='"+config.myChartId+"_chartOptionsLink'></span></td>");
		wrap.push("<td valign='top' style='padding:5px;font-size:14px;font-weight:bold;background-color:#F5F5DC'>Advanced options</td></tr>");
		wrap.push("<tr><td valign='top' style='border-left:1px silver solid;border-top:1px silver solid;padding:3px'>"+wrapBottomLeft+"</td>");
		wrap.push("<td valign='top' style='border-top:1px silver solid;border-right:1px silver solid;padding:3px'>"+wrapBottomRight+"</td></tr>");
		wrap.push("<tr><td colspan='2' valign='top' style='border-left:1px silver solid;border-bottom:1px silver solid;border-right:1px silver solid;text-align:right;padding:3'>"+wrapBottom+"</td></tr></table>");
	wrap = wrap.join('');	

	$("#"+config.myChartId+"_chartConfig").show().html(wrap);
	// Fields
	if(config.listGuid!=''){
		fieldsOnloadOrOnchange(config);
	}else{
		$("#"+config.myChartId+"_labelFieldsDiv").html('Select list first');
		$("#"+config.myChartId+"_numFieldsDiv").html('Select list first');
	}
	// Option link
	var chartConfigLink = setLinkToConfigurationOptions('',config.chartType,config.myChartId);
}

function fieldsOnloadOrOnchange(config,listGuid,chartID){
	if(typeof(config)=='object'){
		var onLoad = true;
		listGuid = config.listGuid;
	}else{
		var onLoad = false;
	}
	var fieldsObj = customGetListFields(listGuid,true);
	var currNumFieldsStrBuffer = [];
	var labelFieldSelectBuffer = [];
	var numFieldSelectBuffer = ["<option value=''>&lt;select&gt;</option>"];
		if(onLoad){
			// Numcols
			currNumFieldsArr = config.numCols.split(';');
			$.each(currNumFieldsArr,function(i,rawVal){
				var split = rawVal.split(',');
				if(split.length>1){
					var label = split[0].split(':')[1];
					var fin = split[1].split(':')[1];
					var action = split[2].split(':')[1];
					var prefix = split[3].split(':')[1];
					var fieldType = split[4].split(':')[1];
					var actionLabel = " ("+action+")";
					if(prefix!='true'){
						var actionLabel = " ("+action+" no prefix)";
					}
					currNumFieldsStrBuffer.push("<span title='Click to remove' style='cursor:pointer;padding-left:10px;' ");
					currNumFieldsStrBuffer.push("fin='"+fin+"' value='"+label+"' action='"+action+"' prefix='"+prefix+"' fieldType='"+fieldType+"' onclick='javascript:$(this).next().andSelf().remove()'>");
					currNumFieldsStrBuffer.push("&bull;&nbsp;"+label+actionLabel+"</span><br />");
				}
			});

			// Build the select for the "string" col
			var labelColSplit = config.labelCol.split(':');
			var strFin = labelColSplit[0];
			var strLabel = labelColSplit[1];

		}

		// Build field selector
		$.each(fieldsObj.labelFields,function(i,rawOpt){
			var split = rawOpt.split('|');
			var fin = split[0];
			var disp = split[1];
			var selected = '';
			if(onLoad){
				if(fin==strFin){
					selected = "selected";
				}
			}
			labelFieldSelectBuffer.push("<option value='"+fin+"' "+selected+">"+disp+"</option>");
		});

		$.each(fieldsObj.numFields,function(i,rawOpt){
			var split = rawOpt.split('|');
			var fin = split[0];
			var disp = split[1];
			var fieldType = split[2];
			numFieldSelectBuffer.push("<option value='"+fin+"' fieldType='"+fieldType+"'>"+disp+"</option>");
		});
		numFieldsBuffer = [];
		labelFields = "<div><select chartID='"+config.myChartId+"'>"+labelFieldSelectBuffer.join('')+"</select></div>";
		if(fieldsObj.onLoad){
			numFieldsBuffer.push(currNumFieldsStrBuffer.join(''));
		}
		numFieldsBuffer.push("<div><select class='numFieldSelect'>"+numFieldSelectBuffer.join('')+"</select>");
		numFieldsBuffer.push("<select class='numActionSelect'>");
		numFieldsBuffer.push("<option value='Sum' selected>Sum</option>");
		numFieldsBuffer.push("<option value='Count'>Count</option>");
		numFieldsBuffer.push("<option value='Average'>Average</option></select>");
		numFieldsBuffer.push("<input title='Uncheck to remove the \"Sum of\", \"Count of\" or \"Average of\" prefix' type='checkbox' checked><label>Prefix</label></div>");
		var numFields = numFieldsBuffer.join('');
	// Load or select
	if(onLoad){
		$("#"+config.myChartId+"_labelFieldsDiv").html(labelFields);
		$("#"+config.myChartId+"_numFieldsDiv").html(numFields);
		$("#"+config.myChartId+"_cloneNumSelectLink").show();
	}else if(!onLoad){
		$("#"+chartID+"_labelFieldsDiv").html(labelFields);
		$("#"+chartID+"_numFieldsDiv").parents('td:first').find('select').each(function(){
			$(this).parent().remove();
		});
		$("#"+chartID+"_numFieldsDiv").html(numFields);
		$("#"+chartID+"_cloneNumSelectLink").show();
	}
}

/*****************************************************
					Get fields
*****************************************************/
function customGetListFields(listName,onLoad){
	xmlStr = [];
	xmlStr.push('<GetList xmlns="http://schemas.microsoft.com/sharepoint/soap/">');
	xmlStr.push('<listName>' + listName + '</listName>');
	xmlStr.push('</GetList>');
	xmlStr = xmlStr.join('');
	var result = {success:false,labelFields:[],numFields:[]};
	wrapSoapRequest(wsBaseUrl + 'lists.asmx', 'http://schemas.microsoft.com/sharepoint/soap/GetList', xmlStr, function(data){
		if($('ErrorText', data).length > 0){
			result.success = false;
		}else{
			result.onLoad = (onLoad==true)?true:false;
			result.success = true;
			result.name = $('List', data).attr('Name');
			var arrOfTypesToIncludeInStr = ['Number','Currency','Text','Calculated','Boolean','User','DateTime','Choice','Lookup'];
			var arrOfTypesToIncludeInNum = ['Number','Currency','Text','Calculated','Boolean','User','DateTime','Choice','Lookup'];
			$('Field', data).each(function(){
				if($(this).attr('DisplayName')!=undefined){
					if($.inArray($(this).attr('Type'),arrOfTypesToIncludeInStr)>-1){
						// Include user created single item lookup
						if($(this).attr('Type')=='Lookup' && $(this).attr('FromBaseType')=='TRUE')return;
						result.labelFields.push($(this).attr('Name')+"|"+$(this).attr('DisplayName'));
					}
					if($.inArray($(this).attr('Type'),arrOfTypesToIncludeInNum)>-1){
						// Include user created single item lookup
						if($(this).attr('Type')=='Lookup' && $(this).attr('FromBaseType')=='TRUE')return;
						result.numFields.push($(this).attr('Name')+"|"+$(this).attr('DisplayName')+"|"+$(this).attr('Type'));
					}
				}
			});
		}
	});
return result;
}

/*****************************************************
				Get list collection
*****************************************************/
function customGetListCollection(){
	xmlStr = '<GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/" />';
	var result = {success:false, errorCode:'', errorText:'internal error', lists:[]};
	wrapSoapRequest(L_Menu_BaseUrl + '/_vti_bin/lists.asmx', 'http://schemas.microsoft.com/sharepoint/soap/GetListCollection', xmlStr, function(data){

		if ($('ErrorText', data).length > 0) {
			result.success = false;
		} else {
			result.success = true;
			var arrTypesToSkip = ['110','111','112','113','114','115','116','117','118'];
			$('List', data).each(function(i){
				if($.inArray($(this).attr('ServerTemplate'),arrTypesToSkip)==-1){
					result.lists.push($(this));
				}
			});
		}
	});
	return result;
}

/*****************************************************
				Get view collection
*****************************************************/
function customGetViewCollection(listGuid){
	xmlStr = "<GetViewCollection xmlns='http://schemas.microsoft.com/sharepoint/soap/'><listName>"+listGuid+"</listName></GetViewCollection>";
	var result = {success:false, errorCode:'', errorText:'internal error', views:[]};
	wrapSoapRequest(L_Menu_BaseUrl + '/_vti_bin/views.asmx', 'http://schemas.microsoft.com/sharepoint/soap/GetViewCollection', xmlStr, function(data){
		if ($('ErrorText', data).length > 0) {
			result.success = false;
		} else {
			result.success = true;
			$('View', data).each(function(i){
				if($(this).attr('Hidden')!='TRUE'){
					result.views.push($(this));
				}
			});
		}
	});
	return result;
}

/*****************************************************
				Wrap webservice call
*****************************************************/
function wrapSoapRequest(webserviceUrl,requestHeader,soapBody,successFunc){
	var xmlWrap = [];
		xmlWrap.push("<?xml version='1.0' encoding='utf-8'?>");
		xmlWrap.push("<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>");
		xmlWrap.push("<soap:Body>");
		xmlWrap.push(soapBody);
		xmlWrap.push("</soap:Body>");
		xmlWrap.push("</soap:Envelope>");
		xmlWrap = xmlWrap.join('');
	$.ajax({
		async:false,
		type:"POST",
		url:webserviceUrl,
		contentType:"text/xml; charset=utf-8",
		processData:false,
		data:xmlWrap,
		dataType:"xml",
		beforeSend:function(xhr){
			xhr.setRequestHeader('SOAPAction',requestHeader);
		},
		success:successFunc,
		error:function(xhr){
			alert(xhr.statusText);
		}
	});
}

/*****************************************************
			Add or remove chart config option
*****************************************************/
function addOrRemove(obj,add){
	if(add){
		var strArr = [];
			strArr.push("<tr>");
			strArr.push("<td style='padding:0 5 0 5'>Option name</td>");
			strArr.push("<td style='padding:0 5 0 5'><div class='newChartConfigDiv'><input title='Option name' style='width:100px' id='NewOptionName' type='Text' />");
			strArr.push("<label style='padding:0 5 0 5'>Value</label><input title='Option value' style='width:100px' id='NewOptionVal' type='Text' />");
			strArr.push("<a title='Remove current option' style='padding-left:5px' href='javascript:' onclick='addOrRemove($(this),false)'>X</a></div>");
			strArr.push("</td></tr>");
			str = strArr.join('');
		obj.parents('tr:first').before(str);
	}else{
		obj.parents('tr:first').remove();
	}
}

/*****************************************************
					Draw chart
*****************************************************/
chart = '';
function drawChart(config,numFieldsArrFromConfig,options) {
var data = new google.visualization.DataTable();
// String
data.addColumn('string',config.labelCol);
// Number
if(config.chartType=='OrgChart'){
	var coltype = 'string';
}else{
	var coltype = 'number';
}
$.each(numFieldsArrFromConfig,function(i,colObj){
var actionFriendlyName = '';
	if(colObj.prefix=='true'){
		switch(colObj.action){
			case 'Sum':
				var actionFriendlyName = 'Sum of ';
			break;
			case 'Count':
				var actionFriendlyName = 'Count of ';
			break;
			case 'Average':
				var actionFriendlyName = 'Average of ';
			break;
			default:
				var actionFriendlyName = '';
		}
	}
	data.addColumn(coltype,actionFriendlyName+colObj.label);
});

	wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
	viewFields = [config.labelCol];
	$.each(numFieldsArrFromConfig,function(i,colObj){
		viewFields.push(colObj.fin);
	});
	if(config.useCustomCAML){
		var customCAML = config.customCAML;
		if(config.useUserProfileProperty){
			userInfoObj = getUserInfo();
			var filterValue = userInfoObj[config.userProfileProperty];
			if(config.regexProfileProperty!='' && filterValue!=''){
				try
				{
					var regExpMatch = filterValue.match(config.regexProfileProperty)[1];
				}
				catch(err)
				{
					window.status="SharePoint Javascripts - Error in RegExp match in chart with ID \""+config.myChartId+"\". The error was: "+err.description;
				}
				if(regExpMatch!=undefined){
					filterValue=regExpMatch;
				}
			}
			if(filterValue==''){
				$("#"+config.myChartId).after("<div style='width:"+options.width+";padding-left:5px;font-size:8px'>The selected user profile property ("+config.userProfileProperty+") is empty!</div>");
			}else{
				$("#"+config.myChartId).after("<div style='width:"+options.width+";padding-left:5px;font-size:8px'>Filtered by user profile property "+config.userProfileProperty+"="+filterValue+"</div>");
			}
			customCAML = customCAML.replace(/\{\}/,filterValue);
		}
		res = queryItems(config.listGuid,customCAML,viewFields);
		if(res.count==-1){
			alert("An error occured in the \"customCAML\" for the chart: "+config.myChartId+".\n"+
				  "Check list guid and CAML query:\n\n"+
				  "ListGuid:\n"+config.listGuid+"\n\n"+
				  "CAML-query:\n"+config.customCAML);
		}
	}else{
		res = queryItemsByViewName(config.listGuid,config.viewGuid,viewFields);
	}
	dataObj = {};
	var rowCount = 0;
	$.each(res.items,function(i,item){
		labelColVal = (item[config.labelCol]!=null)?item[config.labelCol]:'';
		// Strip off any prefix
		if(labelColVal!=null&&labelColVal.indexOf(';#')>-1){
			labelColVal = labelColVal.substring(labelColVal.indexOf(';#')+2);
		}
		if(dataObj[labelColVal]==undefined){
			dataObj[labelColVal]={};
			rowCount ++;
		}
		if(config.chartType=='OrgChart'){
			$.each(numFieldsArrFromConfig,function(idx,obj){
				var thisVal = item[obj.fin];
				// If the source is a calculated column
				if(thisVal!=null&&thisVal.indexOf(';#')>-1){
					thisVal = thisVal.match(/([\d.]+$)/)[0];
				}
				// Build object
				if(dataObj[labelColVal][obj.fin]==undefined){
					var val = (thisVal!=null)?thisVal:null;
					dataObj[labelColVal][obj.fin]=val;
				}
			});
		}else{
			$.each(numFieldsArrFromConfig,function(idx,obj){
				var thisVal = item[obj.fin];
				// If the source is a calculated column - find first number
				if(thisVal!=null&&thisVal.indexOf(';#')>-1){
					thisVal = thisVal.match(/[0-9.-]+/).toString();
				}
				// Build object
				if(dataObj[labelColVal][obj.fin+obj.action]==undefined){
					if(obj.action=='Sum'){
						var val = (thisVal!=null)?parseFloat(thisVal.match(/[0-9.-]+/).toString()):null;
						if(isNaN(val))val=null;
					}else if(obj.action=='Count'){
						if(obj.fieldType=='Boolean'){
							var val = (thisVal==1)?1:0;
						}else{
							var val = (thisVal!=null)?1:0;
						}
					}else if(obj.action=='Average'){
						var val = (thisVal!=null)?parseFloat(thisVal.match(/[0-9.-]+/).toString()):null;
						if(isNaN(val))val=null;
					}
					dataObj[labelColVal][obj.fin+obj.action]={'value':val,'action':obj.action,'count':1};
				}else{
					if(obj.action=='Sum'){
						var val = (thisVal!=null)?parseFloat(thisVal.match(/[0-9.-]+/).toString()):null;
						if(isNaN(val))val=null;
					}else if(obj.action=='Count'){
						if(obj.fieldType=='Boolean'){
							var val = (thisVal==1)?1:0;
						}else{
							var val = (thisVal!=null)?1:0;
						}
					}else if(obj.action=='Average'){
						var val = (thisVal!=null)?parseFloat(thisVal.match(/[0-9.-]+/).toString()):null;
						if(isNaN(val))val=null;
					}
					dataObj[labelColVal][obj.fin+obj.action]['value']+=val;
					dataObj[labelColVal][obj.fin+obj.action]['count']+=1;
				}
			});
		}
	});

	data.addRows(rowCount);
	rowIndex=0;
	if(config.chartType=='OrgChart'){
		$.each(dataObj,function(propName,obj){
			var descr = null;
			// If the name is added with a comma and a description - like "Bruce Springsteen,Bruce Springsteen<br /><div style="text-align:center"><font color="red"><em>The Boss</em></font></div>"
			var split = propName.split(',');
				if(split.length>1){
					propName=split[0];
					descr=split[1];
				}
			data.setCell(rowIndex,0,propName,descr);
			colIndex=1;
			$.each(obj,function(idx,objVal){
				data.setCell(rowIndex,colIndex,objVal);
				colIndex++
			});
			rowIndex++
		});
	}else{
		$.each(dataObj,function(propName,obj){
			data.setValue(rowIndex,0,propName);
			colIndex=1;
			$.each(obj,function(idx,objVal){
				if(objVal.action=='Average'){
					val = objVal.value/objVal.count;
				}else{
					val = objVal.value;
				}
				var roundedVal = Math.round(val*100)/100;
				data.setValue(rowIndex,colIndex,roundedVal);
				colIndex++
			});
			rowIndex++
		});
	}
	// Draw chart
	chart = new google.visualization[config.chartType](document.getElementById(config.myChartId));
	chart.draw(data,options);
	// Add mouse over
	google.visualization.events.addListener(chart,'onmouseover',chartMouseOver);
	google.visualization.events.addListener(chart,'onmouseout',chartMouseOut);
}

function chartMouseOver(e){
	this.setSelection([e]);
}

function chartMouseOut(e){
   this.setSelection([{'row': null, 'column': null}]);
}

/*****************************************************
					Web service calls
*****************************************************/
function queryItemsByViewName(listName, viewName, viewFields, pagingInfo){
	var content = buildQueryContentByViewName(listName, viewName, viewFields, pagingInfo);
	var result = {count:-1, nextPagingInfo:'', items:new Array()};
	innerPost(wsBaseUrl + 'lists.asmx', 'http://schemas.microsoft.com/sharepoint/soap/GetListItems', content, function(data){
		result.count = $('rs\\:data', data).attr('ItemCount');
		result.nextPagingInfo = $('rs\\:data', data).attr('ListItemCollectionPositionNext');
		$('z\\:row', data).each(function(idx, itemData){
			result.items.push(generateItem(itemData, viewFields));
		});
	});
	return result;
}

function buildQueryContentByViewName(listName, viewName, viewFields, pagingInfo){
	var result = new StringBuffer();
	result.append('<GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">');
	result.append('<listName>' + listName + '</listName>');
	result.append('<viewName>' + viewName + '</viewName>');
	if(viewFields != null && viewFields.length > 0){
		result.append('<viewFields><ViewFields xmlns="">');
		$.each(viewFields, function(idx, field){
			result.append('<FieldRef Name="' + field + '"/>');
		});
		result.append('</ViewFields></viewFields>');
	}
	result.append('<queryOptions><QueryOptions xmlns=""><IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>');
	if(pagingInfo != undefined && pagingInfo != null && pagingInfo != '')
		result.append('<Paging ListItemCollectionPositionNext="' + pagingInfo.replace(/&/g, '&amp;') + '" />');
	result.append('</QueryOptions></queryOptions>');
	result.append('</GetListItems>');
	return result.toString();
}

/*****************************************************
	Access user infor for filtering chart data
*****************************************************/
function getUserInfo(UserId){
wsBaseUrl = userListBaseUrl + '/_vti_bin/';
var uiObj = {};

if(typeof(UserId)=="undefined" || UserId=='')UserId = _spUserId;

var arrOfFields = ['ID', 'Name', 'Title', 'EMail', 'Department', 'JobTitle', 'Notes', 'Picture',
'IsSiteAdmin', 'Created', 'Author', 'Modified', 'Editor', 'SipAddress', 'Deleted'];

var item = getItemById(userListGuid,UserId,arrOfFields);
    if(item != null){
	    for(i=0;i<arrOfFields.length;i++){
	    	if(item[arrOfFields[i]]!=null){
	    		uiObj[arrOfFields[i]] = item[arrOfFields[i]];
	    	}else{
	    		uiObj[arrOfFields[i]] = '';
	    	}
	    }
       	return uiObj;
    }else{
        for(i=0;i<arrOfFields.length;i++){
    		uiObj[arrOfFields[i]] = "User with id " + UserId + " not found.";
    	}
		return uiObj;
	}
}

Hover over the code, select “view source”, highlight and copy. Save as “ChartUsingGoogleVisualizationAPI.js”, mind the file extension, and upload to the scriptlibrary as shown above.

Examples

This example shows a web part page with 3 CEWP’s. The top two holds a <div> with unique id’s. The bottom one holds the code that calls the “chart builder” with an array of the id’s of the “containers” set in the top two CEWP’s:


The “container div” can be inserted in a CEWP holding text or other content.

You must ensure is that the CEWP holding the script are placed in the bottom (right) web part zone to ensure that the containers are rendered before the code “needs them”.

The article is not finished and will be updated with examples and more info, please be patient…

Regards
Alexander

Guest Author: Alexander Bautz
SharePoint JavaScripts

Alexander Bautz is a SharePoint consultant/developer (mainly JavaScript/jQuery solution) living in Norway. Alexander spends a lot of his spare time blogging on the same topics. His focus area is "end user customizations" with no (or as little as possible) server side code.

 

Please Join the Discussion

118 Responses to “SharePoint: Interactive Charts Using Google Visualization API”
  1. Ron says:

    HI Alexander

    Awesome Stuff, Looking forward to it.

  2. Xene says:

    Wow, this is a lot to digest on first look, but I’m going to coming back to try this out – maybe I missed it in my initial scan, but it looks like we can implement this without Designer – is that right?

    What about Silverlight?

    Thanks for posting!

  3. Ron says:

    Alexander
    I am getting excepted at line 693 wat could be reason ?please advise me

  4. Eric says:

    I’m getting an error at line 418, when looking at my source code refers to the following:
    call_drawChart(arrOfChartContainers);

    Your effort on this post is appreciated!

  5. Christophe says:

    @Alexander:

    First, kudos for this solution in particular, and for all the helpful posts on SharePoint JavaScripts. I’ve been following you since the beginning, and I am impressed to see how much your content has grown in the past months.

    This leads me to a comment: all the original content published on the Internet is the property of its author, and protected by a copyright. So what is your policy regarding this solution, and generally speaking your other original solutions?

    For the record, people like Paul Grenier, Marc Anderson or myself are using the MIT license. This seems to be the most appropriate for content shared with the community.

    • Hi Christophe,
      Thank you for the kudos and for your inspiration when i first started my blog. For those who do not already know, my first article was published as a “guest author” on Christophe’s blog.

      I have looked at the MIT License and will incorporate it and license my solutions under it. I will include the copyright statement in my next update of the code, but the license is hereby “officially stated”.

      Regards
      Alexander

  6. Jim says:

    These charts are fantastic. Thanks to Alexander’s code and the helpful comments he annotated to it, I have been able to easily produce an organisational chart with names, job roles and photos – something we have really been wanting for a long time here.

    If interested the calculated column formulas (for the chart label column value) used to produce this are below

    For names and job roles

    =[Full Name]&”,”&[Full Name]&”“&[role]&”

    For just roles
    =[Full Name]&”,”&[Full Name]&”“&[role]&”

    (in the org chart options, allowHTML has to be set to true)

    (Am sure that people more proficient at HTML will be able to come up with something better, but this works fine for us here!)

    Cheers

    Jim

    • Jim says:

      Apologies – the formulas did not come out correctly, doesn’t seem to like the html tags…

    • Jim says:

      Thanks Alexander. The calculated column formulas are below

      For photos, names and job roles

      =[Full Name]&","&[Full Name]&"<br /><div style=text-align:center><font color=red><em>"&[role]&"</em></font></div><br /><P align=center><IMG style=""BORDER-RIGHT: 0px solid; BORDER-TOP: 0px solid; BORDER-LEFT: 0px solid; BORDER-BOTTOM: 0px solid""src=""https://mysamplesite/sites/images/"&[Full Name]&".JPG""border=0></P>"
      

      For just names and roles

      =[Full Name]&","&[Full Name]&"<br /><div style=text-align:center><font color=red><em>"&[role]&"</em></font></div>"
      
      • DF says:

        Jim,

        I would like to display name, title, depatment, etc… on my org chart…

        Where exact;y is it that you added the code shown above?

        Thanks!

      • Jim says:

        Hi DF.

        For the organisational chart, I am using a calculated column as my chart label column

        My source list contains fields “Full Name”, “Job Role” and “Reporting to”. I also have a calculated column in which I put in the formula

        =[Full Name]&","&[Full Name]&"<br /><div style=text-align:center><font color=red><em>"&[role]&"</em></font></div>"
        

        For the other code sample used, I have some photos stored in a library on my site, which have the same titles as the names in “Full name”, this allows the photos to be displayed along with the other details within the org chart.

        Hope this helps

      • DF says:

        Jim,

        I added a calculated column (called Name). The column appears to correctly calculate each persons name, title and department (displayed as a sting of HTML).

        Question, how do I get this information to display in my OrgChart? If I specify “Full Name” for my Chart Label Column, the org chart appears as it did before I added the calculated column. If I select my calculated column as my Chart Label Column, only the roles are displayed for the upper portions of my orgchart and only roles and department name are displayed for the leave nodes in my orgchart. What did you use for your Chart Label Column?

        Also, is it possible for me to make the calculated column hidden, and still have the org chart display properly?

        Thanks!

      • Jim says:

        Hi DH,
        For chart label column I have used the calculated column.
        For Chart value column I have field “Reporting to” (count no prefix)
        I have not made any other customisations

        On my list I have the calculated column hidden using one of Alexanders scripts in Dispform and the org chart displays fine.

        Cheers

        Jim

      • DF says:

        Jim,

        I have the column hidden, and the OrgChart displays properly now. The display problem I experienced was due to the incorrectly formed formula used in my calculated column.

        My verbage is too long, maybe I’ll modify the input,, or create additional calculated columns that’ll display more eye friendly abbrivated text :).

        Assuming I can shorten my text, how do I assign different colors for use with each line displayed in the org chart? For exampole, if I wanted to use red, white and blue for each of the 3 rows that make up each cell? (my html must be weak because I keep getting syntax errors :))

        Thanks Again!

  7. Bill Garvey says:

    Alexander – this is exciting stuff.

    I am having one issue – I get “undefined” for the jsapi reference doing the troubleshooting, even though I can navigate to it.

    Help

    • Hi,
      Are you sure the problem is jsapi? – it is the only one with a “fixed” URL, as it is loaded from Google.

      You may save a local copy of the jsapi script to test if it makes any difference.

      Alexander

      • Bill Garvey says:

        Yep – I actually added four alerts – one after each src reference. I get “undefined” followed by three “function” alerts.

        I have tried a local copy, as well as getting and appending jsapi with an API key I got from google – one for each site I tried it on.

        Still “undefined”. I have tried this on my personal SP WSS3 site, as well as my corporate WSS3/MOSS2007 site, in case it was a firewall issue.

        My personal site is http://www.vollieroad.com – take a look. I haven’t pointed the cewp to any list guid yet, just trying to get past the “undefined” alert.

        Oh, and thanks so much for all the great nuggets on your site. I spent most of the day yesterday reading through your entire blog on SharePOint JavaScripts – wish I hade done so before!!

        I work for a large utility, and outside of our corporate SharePoint group, no one can use Designer, so I live and die by the CEWP and JavaScript!!

      • Hi,
        From looking at the site (with the code comment visible at the top of the page) it looks that your copy of the code is corrupt.

        Please try again blike this:

        Hover over the code, select “view source”, highlight and copy. Save as “ChartUsingGoogleVisualizationAPI.js”, mind the file extension, and upload to the scriptlibrary as shown above.

        Alexander

      • Bill Garvey says:

        Thanks so much for looking at this for me – I will reload everything and make sure you can see all the details – will post something when I have it mocked up!

  8. Evan says:

    I implemented this great solution with only one issue. My counts for the bar and column charts are less than the actual counts found in the view. Any ideas?

    • Hi,
      I would appreciate some more details on this one to test the possible bug. Can you provide details in column type, chart setup (count, sum or average), and so on. If you could email me some screen shots it would be great. The email address is found in the top section of the script.

      Also remember that if the view has “paging” configured, the chart will only pull items from the first “batch” of items.

      Alexander

      • Evan says:

        You were exactly correct. I increased the paging record count from 100 to 1000 and the new counts were reflected correctly on the column chart.

        Thanks!

  9. Dwayne says:

    Great site…thank you. However, I can’t get it working. I keep getting an ‘Object expected’ error, and seems related to the call_drawChart(arrOfChartContainers) reference. I’ve tried your debugging link above and the scripts appear to be referenced ok. I’ve doubled-check the GUIDs and they appear ok. Any idea what Object expected could be referring to?

    • Hi,
      My first guess would be that the script is corrupt. Ensure you copy and save the code like it is specified at the bottom of the article.

      Alexander

      • Dwayne says:

        Got the script references working…had to use absolute instead of relative references to the scripts.

        Another question…does this work for lists that can lookup columns? The lookup column in my list does not show up in the dropdown list for Chart value columns.

        thanks

      • Hi,
        Glad you figured it out.

        The code in the original article has been updated to be able to use lookup columns as label column, but it would be possible to include it as a value column as well. I will look into it and post back when the code is updated.

        Alexander

      • Dwayne says:

        Working great in the label column…can’t wait to see it in the value column. I have a Date Time field that is also not showing up in the value column…is that possible?

        thanks again and great work!

    • Hi,
      The code is updated to use the same fields as “label” and “value” columns.

      You find the updated code here

      Alexander

  10. Judd Maroney says:

    This is very cool. Can you give me a starting point on how to modify this to work on a data view web part instead of a list? What I’m trying to do is make this chart dynamic in the sense that it responds to me editing the data view.

    • Judd Maroney says:

      Just to be more specific and accurate: I have some filters attached to my data view web part. I’d like have the graph update when I apply/change a filter value. If I could just reference the DVWP in the chart options, I’d be in great shape (I think). Thanks for the great help.

      Is there a List ID / View ID equivalent for the DVWP?

      • Hi,
        I cannot help you with this at the moment. I might try to accomplish this in a later version, but for the moment i have no solution.

        Alexander

  11. Lee says:

    Amazing, I followed the guide, and it works perfectly!
    I just have a question to ask, how could I change the color of bars in the bar chart?
    It just didn’t match with the theme of my SharePoint team site, but it is really not a big deal!

  12. Dwayne says:

    Again, thanks for your quick reponses to questions…you are doing a killer job!

    Is possible to show data values on the charts?

    • Hi,
      The solution uses Google’s “Visualization API”. There has been released a new version on may 18′th, but i haven’t had the time to update the code. This new update introduces data values on pie chart.

      To test it you can update the CEWP-code like this:

      // Remove this line
      google.load("visualization","1",{packages:["columnchart","barchart","areachart","linechart","piechart","OrgChart"]});
      // And add this
      google.load("visualization","1",{packages:["corechart","OrgChart"]});
      

      Some features has been removed, and some has been added. I will update the code, but cannot promise a specific date…

      Alexander

      • DF says:

        Alexander,

        This works very well. Two additional questions…

        With regards to pie charts, is it possible to arrange the legend so the segment names encompass the pie chart? And afterwards, is it possible to add the lines that run from each segment name (outside the pie) to each of the pie segments?

        Thanks Again!

  13. DF says:

    Alexander,

    It took me a while, but I finally got it working.

    Thanks! This is way cool.

    BTW, I’d like to arrange at least 2 graphs/charts side-by-side. Is there an easy way for me to do this?

    Thanks Again!

    • Hi,
      Yes, just include your “container” in a table with one row and two columns, like this:

      <table>
      <tr>
      <td><div id="MyFirstContainer"></div></td>
      <td><div id="MySecondContainer"></div></td>
      </tr>
      </table>
      

      Alexander

  14. DF says:

    Alexander,

    Almost forgot, there was one issue that I encountered.

    I created 3 graphs (line, area and column) inwhich my x-axis is “Year”.

    For some reason, the years are lists 2010, 2009, 2008, etc… reverse of what I expected to see.

    Is there an easy way to list the years in increasing numerical significance? i.e. 2008, 2009 and 2010?

    Thanks Again!

    • DF says:

      BTW, It’s not a bug in your code… my data is arranged with the newest records located at the top of the list and the oldest records located at the bottom (in reverse order).

      • Hi,
        That’s right, the chart reads the view and reflects any filter or sorting in the view. Use a custom CAML to create a filter/sort setup not found in an existing view.

        Alexander

  15. DF says:

    Alexander,

    I have a main list that I use for data entry and 4 hidden views that I’ve used in the past for displaying graphs/charts, etc…

    With the existing code, I can select my original list, but not the hidden views.

    Is there an easy way for me to modify the code so I can select not only the main list, but also the associated hidden views?

    Thanks!

  16. Christophe says:

    Alexander,
    I was taking a closer look at your code, and saw that you are using a local copy of the Google visualization API. Is this allowed by the terms of use? I read that “You may only use the Visualization API as is and through the Visualization interface provided.”

    The full text:
    http://code.google.com/apis/visualization/terms.html

    • Hi,
      It was not the intention to use a local copy. I couldn’t see where in the code this is done, please point me to where you found it, and i will take a look.

      Alexander

  17. Satish says:

    Hello,
    Thanks for this post. I have implemented this api. But I got stuck at one place. I want to genrate Chart based on user input/project selection.
    Pls,find detail,
    List: (a) Project Name (b) Project Infomation
    I’m genrationing Chart based on project information. Is it any how possible to give one DropDown list let user to select Project Name and based on this I will genrate this chart.

    If possible pls let me know your view.

    Regards,
    Sam

  18. Satish says:

    Hello,
    I want to genrate Chart based on User Input.
    Two Lists:
    1.Project Name
    2.Project Montly Status Report.

    Based on user’s Project Select I want to genrate montly Status Report (eg: Planning Vs Actual)

    I’m able to genrate chart for all Project and for all month’s but I need to genrate as per User’s Selection.

    Help will be really appreciated.

    Thanks,
    Satish

  19. Uttam says:

    /*****************************************************

    Set list Guids and parameters

    *****************************************************/

    // List name or Guid of the config list. This list must reside in the same site as the charts are to be displayed

    chartConfigListGuid =’d4672fa9-3066-4877-8bd6-5947c1561af7′;

    // The “People and Groups” list – must be set to support filtering by user profile data.

    userListGuid = “{570D772F-0EAB-45A8-8C54-9CCD4EC6A0AF}”;

    // Modify if the site collection is on a managed path

    userListBaseUrl = ‘htttp://intranet-stg/stp/’;

    // Each chart must be represented by a container with a unique id. This container must be present in the page

    arrOfChartContainers = ['DemoChart1','DemoChart2'];

    /*****************************************************

    Init charts

    *****************************************************/

    // Load the visualizations from Google

    google.load(”visualization”,”1″,{packages:["columnchart","barchart","areachart","linechart","piechart","OrgChart"]});

    google.setOnLoadCallback(onloadInitChart);

    // Call the script when the visualization packages are loaded

    function onloadInitChart()
    {

    call_drawChart(arrOfChartContainers);

    }

    This is the Code i have written i can see the chart configuration but once i configured the chart it never displays the chart just a blank webpart with the title ..
    Is there anything I am missing

    • Hi,
      Are you sure the list you want to chart has items in the selected view?

      The only error i can spot is the three t’s in “htttp://intranet-stg/stp/”

      Alexander

      • Uttam says:

        yeah i have items in that view but is it necessary to have some values in the
        userListBaseUrl
        i am not able to found what is stopping the chart to be rendered here.
        I am using a task list to display the chart..
        chart type used it Column Chart
        Chart Label Column is Title
        Chart Value Column is Status Sum No prefix
        I am thinking something wrong i am doing in the Chart Configuration but dont know what???

    • Uttam says:

      This is the First Part Of Code Am I Missing Any Refrence to Any Files….
      Beside that my page Loads with a Error at the bottom……
      The Error Details
      Line:925
      Char::7
      Error:’match(……) is null or not an object
      Code:0
      URL:http://intranet-stg/stp/default.aspx

  20. Rafal says:

    Alexander, thanks so much for this stuff. My team’s sharepoint site got something extra and I can look at team’s stats easily.
    One issue only that I have is that it takes quite some time to load the “chart site” – any ideas how can I improve that? Or is it simply because of my corporate firewalls/WANs/etc ?

    • Hi,
      If you have many items in the view the chart is “reading”, it may take some time to process. If the delay is in loading the “visualization api” from Google you may be right in your assumptions, it’s a bit hard to say…

      Try creating a new view with less items and see if the performance is improved.

      Alexander

  21. Ken says:

    Would it be possible to select a list in a subsite?

  22. Lizzy says:

    I have setup the charts and everything looks great – I just havent been able to figure out how to display 2 columns from the same view on a column chart. I would like to display the month an enhancement request was submitted on the horizontal axis and the status the enhancement request is in on the y axis. Is this possible?

  23. Ayodeji says:

    This is very fantastic. I have tried this and it worked perfectly. However, can it also support using google visualization gauge?

  24. Joerg says:

    Thanks for this great tool!
    I use it quite a lot and totaly love it ;)

    Is there a way wehere I can use the vAxis.minValue parameters to define a zero-baseline?

    Thanks in advance,
    Joerg

    • Hi,
      The Google Visualization API has been updates since i wrote this script, and many of the configuration options has changed. To find the compatible “options” for this version, use the
      link found in the top of the Visualization Gallery – or edit the URL like this:
      “/barchart_old.html” – note the “_old” part of the URL.

      I have an updated version on the drawing board, but there is so little time to complete it, so i cannot promise any “delivery date”.

      Alexander

      • Joerg says:

        aaah. thanks alexander!
        parameters “min” and “max” are working perfectly!

        thanks for that and thanks for the sweet tool ;)

        Joerg

  25. DF says:

    Alexander,

    I have one set of charts/graphs that work perfectly.

    Now I want to add a 2nd set of graphs using data from a different SP list…

    Other than changing the List Guid, is there anything else I need to change???

    On my 2nd attempt I’m seeing alot of errors… For example “An error occured while creating the configuration container. The list that is referenced here no longer exists”.

    I confirmed my List Guid… Are there any other gotcha’s I need to look out for?

    Thanks!

    • DF says:

      i.e. If I change the List Guid and container names to match the ones used on my 1st attempt, the charts/graphs display properly… If I change the List guid and container names to the ones used in my 2nd SP list, I receive errors…

      • DF says:

        Alex,

        I figured it out :)

        I only need to specify the correct List Guid and use unique container names….

        Thanks!

  26. DF says:

    Anyone using this code with the “OrgChart” chart type?

    I’m testing it… So far I can create a simple Org Chart, no problem.

    However there are two issues that I’m struggling to resolve.

    1) How to handle the case of a manager that manages two different departments?

    I tried inputting the same managers name twice, specifying a different department for each and grouping my SP List by department name…

    The SP List looks good, but the Google API combined the contents of the two departments when it created the OrgChart… As a result, the two unrelated departments appear as one.

    So far, my only work around is to list the managers name twice, with each name having some additional text added to the end. (i.e. “John Doe 1” and “John Doe 2”)

    It’s ugly, but it works.

    If anyone can recommend a more elegant way of handling the case of a manager whose name appears more than once in the Org Chart, please post your solution.

    2) I’d like for more than just the employee name to be listed… I’d like to list the employee name, and then on the rows beneath that, I’d like to list the department name and job title, etc….

    If possible, I’d also like the ability to add some popup text that causes additional details to be revealed as my mouse cursor hovers over different names.

    So far, I’ve been unsuccessful at adding additional rows of text beneath the employees name.

    I’ve been successful at adding pop-up text to Org Charts in the past, but never before when using this code.

    If anyone has a suggestion for accomplishing either of these tasks, please let me know.

    Thanks!

    • DF says:

      I read the Google Org Chart does not support listing a managers name twice. I also read it’s currently not possible to change the Org Charts orientation from horizonal to vertical.

      I set the configuation option “allowHtml” to “true”, but I’m still trying to figure out whether it’s possible to get the contents of the “Notes” column listed in my SP list to appear as an HTML pop-up…

      And I’m also trying to figure out how to make the SP columns labeled “Department” and “Job Title” to appear beneath each name shown on the Org Chart.

      If anyone has any suggestions, please let me know :)

  27. DeckardCat says:

    Alexander-
    I’ve used this functionality on 2 different sites and trying to add it to a 3rd…but am suddenly receiving an error message on the CEWP:
    Line: 919
    Char: 7
    Error: ‘match(…)’ is null or not an object
    code: 0

    I admit its been awhile since I created the first 2, but for the life of me I can’t figure out what’s wrong! I even re-downloaded /created all of the js files. What am I missing? Thanks.

  28. Tom says:

    Hi Alexander,
    I am using SharePoint MOSS 2003 and wanted to know if your graphics solution will work with SP 2003?
    Thanks,
    Tom

  29. Elizabeth says:

    I need help getting two columns on the same chart. I want mine just like the ‘MyTestChart5′ in the above example pictures. When i view it, it is to blurry to see the configuration. I would like to compare two different severities. Help please!

  30. John says:

    Hi Alexander,

    I’m using these charts and its awesome.

    But, i have little problem:
    I want to see Column chart like you have in “MyTestChart5″. But, I have only two column to use.
    - 1st column is location: Single line of text (For Chart Label Column)
    - 2nd is Pass/Fail column: with YES and NO choice (For Chart Value Column), I use count.

    When I see chart It shows location correctly, but in value it is showing me count of YES+NO, Instead of two different column with count of YES and No.

    Is there any way to see YES and NO count on different column.??

    Thanks.

    • Hi,
      To achieve this you must create two calculated columns to reflect the value “Yes” and “No”, then use those columns in your graph.

      I have plans for a version that separates “choice column values” in separate columns, but have not finished it yet (no delivery date set).

      Alexander

      • John says:

        Hi Alexander,

        I already tried that But, It is also not working.

        It not even showing me chart.

        Thanks for quick reply.

      • Hi,
        Try using “Sum” for the calculated columns – (TRUE = 1 and FALSE = 0).

        Alexander

      • John says:

        Hi Alexander,

        I tried using “Sum” too, but still not working and not showing Chart.

        My calculated columns formula is:
        =IF([Pass/Fail]=”Yes”,”Yes”,”")
        =IF([Pass/Fail]=”No”,”No”,”")
        and both new columns are “Single line of text”.

        Thanks again.

      • Hi,
        Change the code like this:
        =IF(YesNo=TRUE,TRUE,FALSE)
        or
        =IF(YesNo=TRUE,1,0)

        Alexander

      • John says:

        Thanks Alexander now its working great.

        Now, My calucalted columns are:
        =IF([Pass/Fail]=”1”,”1”,”0″)
        =IF([Pass/Fail]=”1”,”1”,”0″)

        - Is there any way I can show both values in one bar (count of Yes and No in one bar instead of two different bar)..??

        Thanks.

      • John says:

        I grt it worked as Stacked,

        Just set Opetion “isStacked” to “true”

        Thansk a lot Alexander.

  31. Paulo says:

    Alexander, very nice work. I was looking for this for a week and it perfectly suited my needs.

    I have a question:
    Is it possible to have a line chart with a gap?
    Example: a line chart that has 2 series of data and one of them stops in the middle.

    Im geting the values that are empty, converted to ZERO wich causes the chart to plot the data as ZERO instead of ending the curve in the middle.

    Could you give me a hint on how to work this out?

    Thanks again and best regards,

    • Paulo says:

      Guys,
      anyone can help?

    • Paulo says:

      I got it !!!

      Before line 988, where the data table that supports the chart is buit cell-by-cell, you should add this line to allow for null values not to be drawn:

      0987 var roundedVal = Math.round(val*100)/100;
      NEW if(val==null){roundedVal=null}
      0988 data.setValue(rowIndex,colIndex,roundedVal);
      0989 colIndex++

      var roundedVal = Math.round(val*100)/100;
      if(val==null){roundedVal=null}
      data.setValue(rowIndex,colIndex,roundedVal);
      colIndex++
      
  32. Tim says:

    Hi Alex,

    I have implemented this solution for a few months within my company and with great results, thanks! However I came back into the office today to find that the graphs aren’t rendering anymore due to “Invalid Argument” error. I’ve found that making the graphs width increase will fix the error, but I was wondering if you had any insight to what might be going on.

    Also, looks like it might be an IE7 error, as the graphs render in Firefox? Just not the kind of Monday I was hoping for to find out all the graphs are not working.

    Thanks!

  33. Nitin Gupta says:

    Hi,

    I tried your solution, it worked great. Thanks for such a nice solution.

    I am not able to display the value on slices (Pie Chart). In chart options I have specified pieSliceText and its value as ‘value’ but still values are not getting displayed on slices.

    I have copied ChartUsingGoogleVisualizationAPI.jsfrom this article only.

    Please tell me if I need to update ChartUsingGoogleVisualizationAPI.js or am I missing anything.

    • Hi,
      As the Google API has been updated this code still uses the older visualizations. You must use the “Older” configurations – see the top of the “config options page” and find the link looking like this: If you need to access the older version, you can find the documentation here

      I’m, working on a updated version that opens up more charts, but it may be past next week before it is published.

      Alexander

  34. Rui says:

    Hi Alexander,

    I’m using this solution for some of our KPIs and its awesome. Thank You.

    I’d like to control the font-size for the labels displayed on the chart, I’m not able to find the right place to make a change. On the bar chart I’m using, the font size is too big for the x-axis. Is it possible for me to control the font size?

    Thanks,
    Rui

  35. Matt says:

    So I was able to get the code to work but I have a different dilemma. I am have a chart that compares 3 years worth of data that I want displayed in 3 lines on the chart. The current year only have 6 months worth of data so the other months have blank values. When the chart renders it shows values of 0 for those months. Is there a way to get the chart to not display a 0 for those months but still show the rest of the data for that year?

    thanks,
    Matt

  36. Paulo says:

    Guys,

    Im trying to use the Webpart with a input filter from a “Choice Filter Web Part”.
    The layout the page looks like this:

    Top Part of the page: a Choice filter (Filter Webpart)
    Bottom Part of the page: Data Table (List Webpart)
    Right Part of the page: The Chart (Alex’s Webpart CEW)

    What I want is to select a value in the Choice Filter and pass the value to the list (Data Table). The chart should understand that since the list data has changed (i.e. is filtered) the chart should be re-processed to reflect the filtered value in the list, and not the entire list.

    I know that this is a “dream” but in a Best Case Scenario we could have the Content Editor Webpart receiving the data via “Edit > Connections > Send Filter Values To > …”.

    I hope I made myself clear enough. If not, please ask.

    Thanks,
    Paulo.

    • Hi,
      This is not possible, sorry.

      The reason for this is that this solution has “nothing to do” with SharePoint webparts and web part connections. If you use a defined view as the datasource, this view is what you get – it is defined in the view schema and does not reflect the filtering.

      To have it “dynamic” you will have to use “Custom Caml” and lookup a value from the user profile to use as a filter to send in with your custom CAML.

      You could build your CAML query from the filter and use it to rebuild the chart, but this would require massive modifications and is not something i have planned for.

      Note that i have published a new version of this tool Interactive Charts using Google Visualization API v2.0

      Alexander

      • Paulo says:

        Thank you Alex! No problem!

        I will use different data sources (views of the same list)… I guess it would be easier!
        Im already playing with the new version of your solution. You have outdone yourself!

        Thank your very much for giving your time to this project.

        Regards,

  37. Lizzie says:

    With the new API it requires me to have two columns listed. 2 of my charts only have one column? How can I change it so it doesnt require me to have 2 columns?

  38. Paulo says:

    Hello there,
    Anyone knows how to use percentage numbers ?

    I use the option to format as a number but only the integer part was understood.
    The decimal part and the percentage suffix were ignored.

    I know that Google recognizes everything as strings but wouldn’t it be possible to format the entry using a formatter call?

    • Paulo says:

      I got it working:

      On ChartUsingGoogleVisualizationAPI_v2.1 before the line where the chart is draw:

      chart.draw(data,options);
      

      we put a call to google.visualization.NumberFormat:

      var formatter = new google.visualization.NumberFormat({suffix: '%',decimalSymbol: ',',fractionDigits: '2'});
      formatter.format(data, 1); // Apply formatter to second column
      formatter.format(data, 2); // Apply formatter to third column
      

      Please not that I put 2 lines to call the format function because I intended to format 2 columns. Adapt as you wish.

      The final code should look like this:

      var formatter = new google.visualization.NumberFormat({suffix: '%',decimalSymbol: ',',fractionDigits: '2'});
      formatter.format(data, 1); // Apply formatter to second column
      formatter.format(data, 2); // Apply formatter to third column
      chart.draw(data,options);
      

      Improvements:
      1) Work on the decimal part of the number: I will multiply my percent number by 100 and that should do it.
      2) Put this option in the web-part GUI.
      3) Extend the format options to DateFormat.

      Reference used:
      http://code.google.com/intl/pt-BR/apis/visualization/documentation/reference.html#numberformatter

      • Paulo says:

        Improvements:
        1) Work on the decimal part of the number:

        this resolved the issue

        thisVal = parseFloat(thisVal*100);

        I tryied to copy all references to “format as number” and adapt to “format as percent” but i got errors, so I leave it hardcoded. I will try to return to this matter and do a function to help upgrading the code.

    • Nice,
      I have not looked at this until now.

      I will incorporate this in the next release.

      Alexander

  39. Nitin Gupta says:

    Hi Alexander,

    I am facing some issues while using “Custom CAML Query”. I am not using “Get filter value from this profile property” option.

    My query goes like this:

    BICEMFG

    After writing this query and saving the same, chart does not show appropriate output. I went to modify shared web part then I saw that query got converted into “BICEMFG” that is concatenation of filter values in the query. I tried many queries with and without Or/And conditions but every time I could see only filter values after saving the web part.

    could you please tell me how should I use custom CAML query?

    This is related to v1.0 only.

    Thanks much,
    Nitin Gupta

    • Hi,
      Is something missing in the comment? – I’m not quite sure what the error is…

      I think however the error could be in line 249.
      Add this line after 249:

      customQueryData = customQueryData.replace(/</g,'&lt;').replace(/>/g,'&gt;');
      

      If this does not work, please look at the v2.0 code.

      Alexander

      • Nitin Gupta says:

        Hi Alexander,

        Query itself is missing in my comment.
        My CAML Query is:

        BICEMFG

        I will try your solution thanks much.
        Nitin Gupta

      • Nitin Gupta says:

        Hi,

        I am trying to post my query here, but due to some reason it is getting replaced by BICEMFG where ‘BI’, ‘CE’ & ‘MFG’ are the filter values of my CAML query.

        My query is:

        where functional area equals ‘BI’ or ‘CE’ or ‘MFG’ ( I am using the same query in CAML format. As I am not able to post my query here so I have written in non technical format).

        Same is happening when I am using CAML query to render the chart.

        Any suggestion what need to be done to resolve this?

        Thanks Alexander,
        Nitin Gupta

    • Hi,
      Email me a screenshot – you find my email in the top of the code.

      Also – look here on how to post code in comments

      Alexander

  40. Tom says:

    Hi Alexander,
    I posted my question earlier but received no response.
    I am using SharePoint MOSS 2003 and wanted to know if your graphics solution will work with SP 2003?
    Thanks,
    Tom

  41. Hi Paulo,
    Please migrate to the new version, as i am very reluctant to do updates on this one…

    If you do, I’m very glad to receive improvement proposals.

    Alexander

  42. Paulo says:

    Alex,
    Im already working with ChartUsingGoogleVisualizationAPI_v2.0.
    Is there a newer version?

  43. Hi,
    No, v2.0 is the newest, but this post is on v1.0 – the v2.0 has not yet been posted here at EUSP – only over at my own blog

    v2.0 will be cross-posted here within a few days i think.

    Alexander

Trackbacks

Check out what others are saying about this post...
  1. [...] SharePoint: Interactive Charts Using Google Visualization API [...]




Notify me of comments to this article:


Speak and you will be heard.

We check comments hourly.
If you want a pic to show with your comment, go get a gravatar!