← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 10966: (DV, PT) Module harmonization + Several minor issues fixed.

 

Merge authors:
  Jan Henrik Øverland (janhenrik-overland)
------------------------------------------------------------
revno: 10966 [merge]
committer: Jan Henrik Overland <janhenrik.overland@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2013-05-23 20:24:54 +0200
message:
  (DV, PT) Module harmonization + Several minor issues fixed.
modified:
  dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js
  dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/i18n.vm


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js	2013-05-22 16:41:30 +0000
+++ dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/app.js	2013-05-23 13:06:37 +0000
@@ -99,8 +99,7 @@
 				dimConf = pt.conf.finals.dimension,
 				dx = dimConf.data.dimensionName,
 				co = dimConf.category.dimensionName,
-				nameDimArrayMap = {},
-				getDimension;
+				nameDimArrayMap = {};
 
 			config.columns = [];
 			config.rows = [];
@@ -141,8 +140,6 @@
 						}
 					}
 					else if (nameDimArrayMap.hasOwnProperty(dimName) && nameDimArrayMap[dimName]) {
-						//axes[i].push(Ext.clone(nameDimArrayMap[dimName]));
-
 						for (var k = 0; k < nameDimArrayMap[dimName].length; k++) {
 							axes[i].push(Ext.clone(nameDimArrayMap[dimName][k]));
 						}
@@ -3712,6 +3709,7 @@
 					xLayout,
 					dimMap,
 					recMap,
+					graphMap,
 					objectName,
 					periodRecords,
 					fixedPeriodRecords = [],
@@ -3848,7 +3846,7 @@
 				userOrganisationUnitChildren.setValue(isOuc);
 
 				// If fav has organisation units, wait for tree callback before update
-				if (recMap[dimConf.organisationUnit.objectName] && graphMap) {
+				if (recMap[dimConf.organisationUnit.objectName] && Ext.isObject(graphMap)) {
 					treePanel.numberOfRecords = pt.util.object.getLength(graphMap);
 					for (var key in graphMap) {
 						if (graphMap.hasOwnProperty(key)) {

=== modified file 'dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js	2013-05-22 16:41:30 +0000
+++ dhis-2/dhis-web/dhis-web-pivot/src/main/webapp/dhis-web-pivot/app/scripts/core.js	2013-05-23 18:19:04 +0000
@@ -555,6 +555,8 @@
 					dimensionNameSortedIdsMap: {}
 				};
 
+			Ext.applyIf(xLayout, layout);
+
 			// Columns, rows, filters
 			if (layout.columns) {
 				for (var i = 0, dim, items, xDim; i < layout.columns.length; i++) {
@@ -711,7 +713,8 @@
 
 			getSyncronizedXLayout = function(xLayout, response) {
 				var removeDimensionFromXLayout,
-					getHeaderNames;
+					getHeaderNames,
+					dimensions = [].concat(xLayout.columns, xLayout.rows, xLayout.filters);
 
 				removeDimensionFromXLayout = function(objectName) {
 					var getUpdatedAxis;
@@ -759,16 +762,30 @@
 						co = dimConf.category.objectName,
 						layout;
 
+					// Use metaData ids if any
+					for (var i = 0, dim, metaDataDim, items; i < dimensions.length; i++) {
+						dim = dimensions[i];
+						metaDataDim = response.metaData[dim.objectName];
+
+						if (Ext.isArray(metaDataDim)) {
+							items = [];
+
+							for (var j = 0; j < metaDataDim.length; j++) {
+								items.push({id: metaDataDim[j]});
+							}
+
+							dim.items = items;
+						}
+					}
+
 					// Remove co from layout if it does not exist in response
 					if (Ext.Array.contains(xLayout.axisDimensionNames, co) && !(Ext.Array.contains(headerNames, co))) {
 						removeDimensionFromXLayout(co);
-
-						layout = pt.api.layout.Layout(xLayout);
-
-						return layout ? pt.util.pivot.getExtendedLayout(layout) : null;
 					}
 
-					return xLayout;
+					layout = pt.api.layout.Layout(xLayout);
+
+					return layout ? pt.util.pivot.getExtendedLayout(layout) : null;
 				}();
 			};
 
@@ -827,28 +844,32 @@
 			getExtendedResponse = function(response, xLayout) {
 				response.nameHeaderMap = {};
 				response.idValueMap = {};
+				ids = [];
 
 				var extendHeaders = function() {
 
 					// Extend headers: index, items, size
 					for (var i = 0, header; i < response.headers.length; i++) {
 						header = response.headers[i];
+
+						// Index
 						header.index = i;
 
 						if (header.meta) {
 
-							// categories
-							if (header.name === pt.conf.finals.dimension.category.dimensionName) {
-								header.items = [].concat(response.metaData[pt.conf.finals.dimension.category.dimensionName]);
-							}
-							// periods
-							else if (header.name === pt.conf.finals.dimension.period.dimensionName) {
-								header.items = [].concat(response.metaData[pt.conf.finals.dimension.period.dimensionName]);
-							}
+							// Items: get ids from metadata
+							if (Ext.isArray(response.metaData[header.name])) {
+								header.items = Ext.clone(response.metaData[header.name]);
+							}
+							// Items: get ids from xLayout
 							else {
 								header.items = xLayout.dimensionNameIdsMap[header.name];
 							}
 
+							// Collect ids
+							ids = ids.concat(header.items);
+
+							// Size
 							header.size = header.items.length;
 						}
 					}
@@ -861,6 +882,17 @@
 					}
 				}();
 
+				var extendMetaData = function() {
+					for (var i = 0, id, splitId ; i < ids.length; i++) {
+						id = ids[i];
+
+						if (id.indexOf('-') !== -1) {
+							splitId = id.split('-');
+							response.metaData.names[id] = response.metaData.names[splitId[0]] + ' ' + response.metaData.names[splitId[1]];
+						}
+					}
+				}();
+
 				var createValueIds = function() {
 					var valueHeaderIndex = response.nameHeaderMap[pt.conf.finals.dimension.value.value].index,
 						dimensionNames = xLayout.axisDimensionNames,
@@ -1778,7 +1810,7 @@
 			}
 
 			Ext.Ajax.request({
-				url: pt.baseUrl + '/api/reportTables/' + id + '.json?links=false&viewClass=dimensional',
+				url: pt.baseUrl + '/api/reportTables/' + id + '.json?viewClass=dimensional&links=false',
 				method: 'GET',
 				failure: function(r) {
 					pt.util.mask.hideMask();
@@ -1798,7 +1830,7 @@
 	return util;
 };
 
-PT.core.getAPI = function(pt) {
+PT.core.getApi = function(pt) {
 	var dimConf = pt.conf.finals.dimension,
 		api = {
 			layout: {
@@ -1914,7 +1946,7 @@
 
 		// userOrganisationUnitChildren: boolean (false)
 
-		// parentGraphMap: string ('')
+		// parentGraphMap: object
 
 		// reportingPeriod: boolean (false) //report tables only
 
@@ -2001,11 +2033,11 @@
 			layout.userOrganisationUnit = Ext.isBoolean(config.userOrganisationUnit) ? config.userOrganisationUnit : false;
 			layout.userOrganisationUnitChildren = Ext.isBoolean(config.userOrganisationUnitChildren) ? config.userOrganisationUnitChildren : false;
 
-			layout.parentGraphMap = Ext.isString(config.parentGraphMap) ? config.parentGraphMap : '';
+			layout.parentGraphMap = Ext.isObject(config.parentGraphMap) ? config.parentGraphMap : undefined;
 
-			layout.reportingPeriod = Ext.isBoolean(config.reportParams.paramReportingPeriod) ? config.reportParams.paramReportingPeriod : (Ext.isBoolean(config.reportingPeriod) ? config.reportingPeriod : false);
-			layout.organisationUnit = Ext.isBoolean(config.reportParams.paramOrganisationUnit) ? config.reportParams.paramOrganisationUnit : (Ext.isBoolean(config.organisationUnit) ? config.organisationUnit : false);
-			layout.parentOrganisationUnit = Ext.isBoolean(config.reportParams.paramParentOrganisationUnit) ? config.reportParams.paramParentOrganisationUnit : (Ext.isBoolean(config.parentOrganisationUnit) ? config.parentOrganisationUnit : false);
+			layout.reportingPeriod = Ext.isObject(config.reportParams) && Ext.isBoolean(config.reportParams.paramReportingPeriod) ? config.reportParams.paramReportingPeriod : (Ext.isBoolean(config.reportingPeriod) ? config.reportingPeriod : false);
+			layout.organisationUnit =  Ext.isObject(config.reportParams) && Ext.isBoolean(config.reportParams.paramOrganisationUnit) ? config.reportParams.paramOrganisationUnit : (Ext.isBoolean(config.organisationUnit) ? config.organisationUnit : false);
+			layout.parentOrganisationUnit =  Ext.isObject(config.reportParams) && Ext.isBoolean(config.reportParams.paramParentOrganisationUnit) ? config.reportParams.paramParentOrganisationUnit : (Ext.isBoolean(config.parentOrganisationUnit) ? config.parentOrganisationUnit : false);
 
 			layout.regression = Ext.isBoolean(config.regression) ? config.regression : false;
 			layout.cumulative = Ext.isBoolean(config.cumulative) ? config.cumulative : false;
@@ -2112,7 +2144,7 @@
 
 	pt.conf = PT.core.getConfigs();
 	pt.util = PT.core.getUtils(pt);
-	pt.api = PT.core.getAPI(pt);
+	pt.api = PT.core.getApi(pt);
 
 	return pt;
 };

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties	2013-05-13 14:00:23 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/resources/org/hisp/dhis/visualizer/i18n_module.properties	2013-05-23 16:34:57 +0000
@@ -190,4 +190,5 @@
 can_view=Can view
 can_edit_and_view=Can edit and view
 search_for_user_groups=Search for user group
-public_access=Public access
\ No newline at end of file
+public_access=Public access
+detailed_data_elements_cannot_be_specified_as_filter=Detailed data elements cannot be specified as filter
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js	2013-05-22 14:06:23 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/app.js	2013-05-23 18:19:04 +0000
@@ -115,52 +115,57 @@
 
 		util.chart.getLayoutConfig = function() {
 			var panels = dv.cmp.dimension.panels,
-				seriesDimensionName = dv.viewport.series.getValue(),
-				categoryDimensionName = dv.viewport.category.getValue(),
-				filterDimensionNames = dv.viewport.filter.getValue(),
+				columnDimNames = [dv.viewport.series.getValue()],
+				rowDimNames = [dv.viewport.category.getValue()],
+				filterDimNames = dv.viewport.filter.getValue(),
 				config = dv.viewport.optionsWindow.getOptions(),
-				getDimension;
+				dimConf = dv.conf.finals.dimension,
+				dx = dimConf.data.dimensionName,
+				co = dimConf.category.dimensionName,
+				nameDimArrayMap = {};
+
+			config.type = dv.viewport.chartType.getChartType();
 
 			config.columns = [];
 			config.rows = [];
 			config.filters = [];
 
-			getDimension = function(config) {
-				if (dv.api.objectNameDimensionClassMap[config.dimension]) {
-					return dv.api.objectNameDimensionClassMap[config.dimension](config);
-				}
-				else {
-					return dv.api.Dimension(config);
-				}
-			};
+			// Panel data
+			for (var i = 0, dim, dimName; i < panels.length; i++) {
+				dim = panels[i].getDimension();
+
+				if (dim) {
+					nameDimArrayMap[dim.dimension] = [dim];
+				}
+			}
+
+			nameDimArrayMap[dx] = Ext.Array.clean([].concat(
+				nameDimArrayMap[dimConf.indicator.objectName],
+				nameDimArrayMap[dimConf.dataElement.objectName],
+				nameDimArrayMap[dimConf.operand.objectName],
+				nameDimArrayMap[dimConf.dataSet.objectName]
+			));
 
 			// Columns, rows, filters
-			for (var i = 0, dim; i < panels.length; i++) {
-				dim = panels[i].getData();
-
-				if (dim) {
-					if (dim.dimensionName === seriesDimensionName) {
-						config.columns.push(getDimension({
-							dimension: dim.objectName,
-							items: dim.items
-						}));
-					}
-					else if (dim.dimensionName === categoryDimensionName) {
-						config.rows.push(getDimension({
-							dimension: dim.objectName,
-							items: dim.items
-						}));
-					}
-					else if (Ext.Array.contains(filterDimensionNames, dim.dimensionName)) {
-						config.filters.push(getDimension({
-							dimension: dim.objectName,
-							items: dim.items
-						}));
+			for (var i = 0, nameArrays = [columnDimNames, rowDimNames, filterDimNames], axes = [config.columns, config.rows, config.filters], dimNames; i < nameArrays.length; i++) {
+				dimNames = nameArrays[i];
+
+				for (var j = 0, dimName, dim; j < dimNames.length; j++) {
+					dimName = dimNames[j];
+
+					if (dimName === dx && nameDimArrayMap.hasOwnProperty(dimName) && nameDimArrayMap[dimName]) {
+						for (var k = 0; k < nameDimArrayMap[dx].length; k++) {
+							axes[i].push(Ext.clone(nameDimArrayMap[dx][k]));
+						}
+					}
+					else if (nameDimArrayMap.hasOwnProperty(dimName) && nameDimArrayMap[dimName]) {
+						for (var k = 0; k < nameDimArrayMap[dimName].length; k++) {
+							axes[i].push(Ext.clone(nameDimArrayMap[dimName][k]));
+						}
 					}
 				}
 			}
 
-			config.type = dv.viewport.chartType.getChartType();
 			config.userOrganisationUnit = dv.viewport.userOrganisationUnit.getValue();
 			config.userOrganisationUnitChildren = dv.viewport.userOrganisationUnitChildren.getValue();
 
@@ -838,7 +843,7 @@
 
 		hideTitle = Ext.create('Ext.form.field.Checkbox', {
 			boxLabel: DV.i18n.hide_chart_title,
-			style: 'margin-bottom:6px',
+			style: 'margin-bottom:7px',
 			listeners: {
 				change: function() {
 					title.xable();
@@ -853,7 +858,6 @@
 			width: 310,
 			fieldLabel: DV.i18n.chart_title,
 			fieldLabel: 'Chart title',
-			labelStyle: 'padding-left:16px',
 			labelWidth: 123,
 			maxLength: 100,
 			enforceMaxLength: true,
@@ -868,7 +872,6 @@
 			style: 'margin-bottom:2px; margin-left:2px',
 			width: 310,
 			fieldLabel: DV.i18n.domain_axis_label,
-			labelStyle: 'padding-left:16px',
 			labelWidth: 123,
 			maxLength: 100,
 			enforceMaxLength: true
@@ -880,7 +883,6 @@
 			style: 'margin-bottom:0; margin-left:2px',
 			width: 310,
 			fieldLabel: DV.i18n.range_axis_label,
-			labelStyle: 'padding-left:16px',
 			labelWidth: 123,
 			maxLength: 100,
 			enforceMaxLength: true
@@ -899,7 +901,7 @@
 					bodyStyle: 'border:0 none',
 					items: [
 						{
-							bodyStyle: 'border:0 none; padding-top:3px; padding-left:18px; margin-right:5px',
+							bodyStyle: 'border:0 none; padding-top:3px; padding-left:2px; margin-right:5px',
 							width: 130,
 							html: 'Target value / title:'
 						},
@@ -913,7 +915,7 @@
 					bodyStyle: 'border:0 none',
 					items: [
 						{
-							bodyStyle: 'border:0 none; padding-top:3px; padding-left:18px; margin-right:5px',
+							bodyStyle: 'border:0 none; padding-top:3px; padding-left:2px; margin-right:5px',
 							width: 130,
 							html: 'Base value / title:'
 						},
@@ -1115,10 +1117,12 @@
 		});
 
 		getBody = function() {
-			var favorite = Ext.clone(dv.xLayout);
+			var favorite,
+				dimensions;
 
-			if (favorite) {
-				var dimensions = [].concat(favorite.columns, favorite.rows, favorite.filters);
+			if (dv.layout) {
+				favorite = Ext.clone(dv.layout);
+				dimensions = [].concat(favorite.columns, favorite.rows, favorite.filters);
 
 				// Server sync: property names
 				favorite.showData = favorite.showValues;
@@ -1139,8 +1143,6 @@
 				favorite.rangeAxisLabel = favorite.rangeAxisTitle;
 				delete favorite.rangeAxisTitle;
 
-				delete favorite.extended;
-
 				// Replace operand id characters
 				for (var i = 0; i < dimensions.length; i++) {
 					if (dimensions[i].dimension === dv.conf.finals.dimension.operand.objectName) {
@@ -1149,27 +1151,9 @@
 						}
 					}
 				}
-
-				// Server sync: user orgunit
-				if (favorite.userOrganisationUnit || favorite.userOrganisationUnitChildren) {
-					var dimensions = [].concat(favorite.columns, favorite.rows, favorite.filters);
-
-					for (var i = 0; i < dimensions.length; i++) {
-						if (dimensions[i].dimension === dv.conf.finals.dimension.organisationUnit.objectName) {
-							if (favorite.userOrganisationUnit) {
-								dimensions[i].items.push({id: 'USER_ORGUNIT'});
-							}
-							if (favorite.userOrganisationUnitChildren) {
-								dimensions[i].items.push({id: 'USER_ORGUNIT_CHILDREN'});
-							}
-						}
-					}
-				}
-
-				return favorite;
 			}
 
-			return;
+			return favorite;
 		};
 
 		NameWindow = function(id) {
@@ -1196,32 +1180,29 @@
 					var favorite = getBody();
 					favorite.name = nameTextfield.getValue();
 
-					if (!(favorite && favorite.name)) {
-						return;
+					if (favorite && favorite.name) {
+						Ext.Ajax.request({
+							url: dv.baseUrl + '/api/charts/',
+							method: 'POST',
+							headers: {'Content-Type': 'application/json'},
+							params: Ext.encode(favorite),
+							failure: function(r) {
+								dv.util.mask.hideMask();
+								alert(r.responseText);
+							},
+							success: function(r) {
+								var id = r.getAllResponseHeaders().location.split('/').pop();
+
+								dv.favorite = favorite;
+
+								dv.store.charts.loadStore();
+
+								//dv.viewport.interpretationButton.enable();
+
+								window.destroy();
+							}
+						});
 					}
-
-					// Request
-					Ext.Ajax.request({
-						url: dv.init.contextPath + '/api/charts/',
-						method: 'POST',
-						headers: {'Content-Type': 'application/json'},
-						params: Ext.encode(favorite),
-						failure: function(r) {
-							dv.util.mask.hideMask();
-							alert(r.responseText);
-						},
-						success: function(r) {
-							var id = r.getAllResponseHeaders().location.split('/').pop();
-
-							dv.favorite = favorite;
-
-							dv.store.charts.loadStore();
-
-							//dv.viewport.interpretationButton.enable();
-
-							window.destroy();
-						}
-					});
 				}
 			});
 
@@ -2338,18 +2319,17 @@
 				xtype: 'panel',
 				title: '<div class="dv-panel-title-data">' + DV.i18n.indicators + '</div>',
 				hideCollapseTool: true,
-				getData: function() {
-					var data = {
-						dimensionName: dv.conf.finals.dimension.indicator.dimensionName,
-						objectName: dv.conf.finals.dimension.indicator.objectName,
+				getDimension: function() {
+					var config = {
+						dimension: dv.conf.finals.dimension.indicator.objectName,
 						items: []
 					};
 
 					dv.store.indicatorSelected.each( function(r) {
-						data.items.push({id: r.data.id});
+						config.items.push({id: r.data.id});
 					});
 
-					return data.items.length ? data : null;
+					return config.items.length ? config : null;
 				},
 				onExpand: function() {
 					var h = dv.viewport.westRegion.hasScrollbar ?
@@ -2624,27 +2604,17 @@
 				xtype: 'panel',
 				title: '<div class="dv-panel-title-data">' + DV.i18n.data_elements + '</div>',
 				hideCollapseTool: true,
-				getData: function() {
-					var optionComboIds = [],
-						data = {
-							dimensionName: dv.conf.finals.dimension.dataElement.dimensionName,
-							objectName: dataElementDetailLevel.getValue(),
-							items: []
-						};
+				getDimension: function() {
+					var config = {
+						dimension: dataElementDetailLevel.getValue(),
+						items: []
+					};
 
 					dv.store.dataElementSelected.each( function(r) {
-						data.items.push({id: r.data.id});
-
-						if (dataElementDetailLevel.getValue() === dv.conf.finals.dimension.operand.objectName) {
-							optionComboIds.push(r.data.optionComboId);
-						}
+						config.items.push({id: r.data.id});
 					});
 
-					if (optionComboIds.length) {
-						data.optionComboIds = optionComboIds;
-					}
-
-					return data.items.length ? data : null;
+					return config.items.length ? config : null;
 				},
 				onExpand: function() {
 					var h = dv.viewport.westRegion.hasScrollbar ?
@@ -2768,18 +2738,17 @@
 				xtype: 'panel',
 				title: '<div class="dv-panel-title-data">' + DV.i18n.reporting_rates + '</div>',
 				hideCollapseTool: true,
-				getData: function() {
-					var data = {
-						dimensionName: dv.conf.finals.dimension.dataSet.dimensionName,
-						objectName: dv.conf.finals.dimension.dataSet.objectName,
+				getDimension: function() {
+					var config = {
+						dimension: dv.conf.finals.dimension.dataSet.objectName,
 						items: []
 					};
 
 					dv.store.dataSetSelected.each( function(r) {
-						data.items.push({id: r.data.id});
+						config.items.push({id: r.data.id});
 					});
 
-					return data.items.length ? data : null;
+					return config.items.length ? config : null;
 				},
 				onExpand: function() {
 					var h = dv.viewport.westRegion.hasScrollbar ?
@@ -3235,25 +3204,24 @@
 				xtype: 'panel',
 				title: '<div class="dv-panel-title-period">Periods</div>',
 				hideCollapseTool: true,
-				getData: function() {
-					var data = {
-							dimensionName: dv.conf.finals.dimension.period.dimensionName,
-							objectName: dv.conf.finals.dimension.period.objectName,
+				getDimension: function() {
+					var config = {
+							dimension: dv.conf.finals.dimension.period.objectName,
 							items: []
 						},
 						chb = dv.cmp.dimension.relativePeriod.checkbox;
 
 					dv.store.fixedPeriodSelected.each( function(r) {
-						data.items.push({id: r.data.id});
+						config.items.push({id: r.data.id});
 					});
 
 					for (var i = 0; i < chb.length; i++) {
 						if (chb[i].getValue()) {
-							data.items.push({id: chb[i].relativePeriodId});
+							config.items.push({id: chb[i].relativePeriodId});
 						}
 					}
 
-					return data.items.length ? data : null;
+					return config.items.length ? config : null;
 				},
 				onExpand: function() {
 					var h = dv.viewport.westRegion.hasScrollbar ?
@@ -3520,29 +3488,28 @@
 				bodyStyle: 'padding-top:5px',
 				hideCollapseTool: true,
 				collapsed: false,
-				getData: function() {
+				getDimension: function() {
 					var r = treePanel.getSelectionModel().getSelection(),
-						data = {
-							dimensionName: dv.conf.finals.dimension.organisationUnit.dimensionName,
-							objectName: dv.conf.finals.dimension.organisationUnit.objectName,
+						config = {
+							dimension: dv.conf.finals.dimension.organisationUnit.objectName,
 							items: []
 						};
 
 					if (userOrganisationUnit.getValue() || userOrganisationUnitChildren.getValue()) {
 						if (userOrganisationUnit.getValue()) {
-							data.items.push({id: 'USER_ORGUNIT'});
+							config.items.push({id: 'USER_ORGUNIT'});
 						}
 						if (userOrganisationUnitChildren.getValue()) {
-							data.items.push({id: 'USER_ORGUNIT_CHILDREN'});
+							config.items.push({id: 'USER_ORGUNIT_CHILDREN'});
 						}
 					}
 					else {
 						for (var i = 0; i < r.length; i++) {
-							data.items.push({id: r[i].data.id});
+							config.items.push({id: r[i].data.id});
 						}
 					}
 
-					return data.items.length ? data : null;
+					return config.items.length ? config : null;
 				},
 				onExpand: function() {
 					var h = dv.viewport.westRegion.hasScrollbar ?
@@ -3730,18 +3697,17 @@
 						hideCollapseTool: true,
 						availableStore: availableStore,
 						selectedStore: selectedStore,
-						getData: function() {
-							var data = {
-								dimensionName: dimension.id,
-								objectName: dimension.id,
+						getDimension: function() {
+							var config = {
+								dimension: dimension.id,
 								items: []
 							};
 
 							selectedStore.each( function(r) {
-								data.items.push({id: r.data.id});
+								config.items.push({id: r.data.id});
 							});
 
-							return data.items.length ? data : null;
+							return config.items.length ? config : null;
 						},
 						onExpand: function() {
 							if (!availableStore.isLoaded) {
@@ -3806,11 +3772,31 @@
 					objectNameDimensionMap[dimensions[i].dimension] = dimensions[i];
 				}
 
-				// Indicator as filter
-				for (var i = 0; i < layout.filters.length; i++) {
-					if (layout.filters[i].dimension === dimConf.indicator.objectName) {
-						alert(DV.i18n.indicators_cannot_be_specified_as_filter);
-						return;
+				if (!layout) {
+					return;
+				}
+
+				if (layout.filters && layout.filters.length) {
+					for (var i = 0; i < layout.filters.length; i++) {
+
+						// Indicators as filter
+						if (layout.filters[i].dimension === dimConf.indicator.objectName) {
+							alert(DV.i18n.indicators_cannot_be_specified_as_filter);
+							return;
+						}
+
+						// Categories as filter
+						//if (layout.filters[i].dimension === dimConf.category.objectName) {
+							//alert(PT.i18n.categories_cannot_be_specified_as_filter);
+							//return;
+						//}
+
+						// Operands as filter
+						if (layout.filters[i].dimension === dimConf.operand.objectName) {
+							alert(DV.i18n.detailed_data_elements_cannot_be_specified_as_filter);
+							return;
+						}
+
 					}
 				}
 
@@ -3853,11 +3839,8 @@
 
 			update = function() {
 				var config = dv.util.chart.getLayoutConfig(),
-					layout = dv.api.Layout(config);
+				layout = dv.api.layout.Layout(config);
 
-				if (!layout) {
-					return;
-				}
 				if (!validateSpecialCases(layout)) {
 					return;
 				}
@@ -4099,38 +4082,31 @@
 				}
 			});
 
-			setFavorite = function(xLayout) {
-				var seriesId = xLayout.extended.columnsDimensionNames[0],
-					categoryId = xLayout.extended.rowsDimensionNames[0],
-					filterIds = xLayout.extended.filtersDimensionNames,
-					dimMap = xLayout.extended.objectNameDimensionMap,
-					recMap = xLayout.extended.objectNameRecordsMap,
-					graphMap = xLayout.extended.parentGraphMap,
-					dimConf = dv.conf.finals.dimension,
+			setFavorite = function(layout) {
+				var dimConf = dv.conf.finals.dimension,
+					xLayout,
+					dimMap,
+					recMap,
+					graphMap,
 					objectName,
 					periodRecords,
 					fixedPeriodRecords = [],
 					isOu = false,
 					isOuc = false;
 
-				// Type
-				dv.viewport.chartType.setChartType(xLayout.type);
-
-				// Series, category, filter
-				dv.viewport.series.setValue(seriesId);
-				dv.viewport.series.filterNext();
-
-				dv.viewport.category.setValue(categoryId);
-				dv.viewport.category.filterNext();
-
-				dv.viewport.filter.setValue(filterIds);
+				dv.util.chart.createChart(layout, dv);
+
+				xLayout = dv.util.chart.getExtendedLayout(layout);
+				dimMap = xLayout.objectNameDimensionsMap;
+				recMap = xLayout.objectNameItemsMap;
+				graphMap = layout.parentGraphMap;
 
 				// Indicators
 				dv.store.indicatorSelected.removeAll();
 				objectName = dimConf.indicator.objectName;
 				if (dimMap[objectName]) {
 					dv.store.indicatorSelected.add(Ext.clone(recMap[objectName]));
-					pt.util.multiselect.filterAvailable({store: pt.store.indicatorAvailable}, {store: pt.store.indicatorSelected});
+					dv.util.multiselect.filterAvailable({store: dv.store.indicatorAvailable}, {store: dv.store.indicatorSelected});
 				}
 
 				// Data elements
@@ -4138,7 +4114,7 @@
 				objectName = dimConf.dataElement.objectName;
 				if (dimMap[objectName]) {
 					dv.store.dataElementSelected.add(Ext.clone(recMap[objectName]));
-					pt.util.multiselect.filterAvailable({store: pt.store.dataElementAvailable}, {store: pt.store.dataElementSelected});
+					dv.util.multiselect.filterAvailable({store: dv.store.dataElementAvailable}, {store: dv.store.dataElementSelected});
 					dv.viewport.dataElementDetailLevel.setValue(objectName);
 				}
 
@@ -4146,7 +4122,7 @@
 				objectName = dimConf.operand.objectName;
 				if (dimMap[objectName]) {
 					dv.store.dataElementSelected.add(Ext.clone(recMap[objectName]));
-					pt.util.multiselect.filterAvailable({store: pt.store.dataSetAvailable}, {store: pt.store.dataSetSelected});
+					dv.util.multiselect.filterAvailable({store: dv.store.dataSetAvailable}, {store: dv.store.dataSetSelected});
 					dv.viewport.dataElementDetailLevel.setValue(objectName);
 				}
 
@@ -4172,7 +4148,7 @@
 					}
 				}
 				dv.store.fixedPeriodSelected.add(fixedPeriodRecords);
-				pt.util.multiselect.filterAvailable({store: pt.store.fixedPeriodAvailable}, {store: pt.store.fixedPeriodSelected});
+				dv.util.multiselect.filterAvailable({store: dv.store.fixedPeriodAvailable}, {store: dv.store.fixedPeriodSelected});
 
 				// Group sets
 				for (var key in dimensionIdSelectedStoreMap) {
@@ -4192,8 +4168,19 @@
 					}
 				}
 
+				// Layout
+				dv.viewport.chartType.setChartType(xLayout.type);
+
+				dv.viewport.series.setValue(xLayout.columnDimensionNames[0]);
+				dv.viewport.series.filterNext();
+
+				dv.viewport.category.setValue(xLayout.rowDimensionNames[0]);
+				dv.viewport.category.filterNext();
+
+				dv.viewport.filter.setValue(xLayout.filterDimensionNames);
+
 				// Options
-				dv.viewport.optionsWindow.setOptions(xLayout);
+				dv.viewport.optionsWindow.setOptions(layout);
 
 				// Organisation units
 				if (recMap[dimConf.organisationUnit.objectName]) {
@@ -4211,7 +4198,7 @@
 				userOrganisationUnitChildren.setValue(isOuc);
 
 				// If fav has organisation units, wait for tree callback before update
-				if (recMap[dimConf.organisationUnit.objectName] && graphMap) {
+				if (recMap[dimConf.organisationUnit.objectName] && Ext.isObject(graphMap)) {
 					treePanel.numberOfRecords = dv.util.object.getLength(graphMap);
 					for (var key in graphMap) {
 						if (graphMap.hasOwnProperty(key)) {
@@ -4221,7 +4208,7 @@
 				}
 				else {
 					treePanel.reset();
-					update();
+					//update();
 				}
 			};
 

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js	2013-05-15 13:00:34 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/app/scripts/core.js	2013-05-23 18:19:04 +0000
@@ -417,188 +417,208 @@
 	};
 
 	util.chart = {
-		extendLayout: function(layout) {
-			var xLayout = Ext.clone(layout),
-				dimConf = dv.conf.finals.dimension,
-
-				axisDimensions = [].concat(Ext.clone(xLayout.columns) || [], Ext.clone(xLayout.rows) || []),
-				axisDimensionNames = [],
-				axisObjectNames = [],
-				axisItems = [],
-
-				filterDimensions = Ext.clone(xLayout.filters) || [],
-				filterDimensionNames = [],
-				filterObjectNames = [],
-				filterItems = [],
-
-				objectNameDimensionMap = {},
-				dimensionNameItemsMap = {},
-
-				columns,
-				columnsDimensionNames = [],
-				rows,
-				rowsDimensionNames = [],
-				filters,
-				filtersDimensionNames = [],
-
-				objectNameRecordsMap = {};
-
-			xLayout.extended = {};
-
-			// Axis
-			for (var i = 0, dim, items; i < axisDimensions.length; i++) {
-				dim = axisDimensions[i];
-				items = [];
-
-				dim.dimensionName = dv.conf.finals.dimension.objectNameMap[dim.dimension].dimensionName;
-				dim.objectName = dim.dimension;
-
-				axisDimensionNames.push(dim.dimensionName);
-				axisObjectNames.push(dim.objectName);
-
-				for (var j = 0; j < dim.items.length; j++) {
-					items.push(dim.items[j].id);
-				}
-
-				dim.items = items;
-
-				axisItems = axisItems.concat(items);
-			}
-
-			// Filter
-			for (var i = 0, dim, items; i < filterDimensions.length; i++) {
-				dim = filterDimensions[i];
-				items = [];
-
-				dim.dimensionName = dv.conf.finals.dimension.objectNameMap[dim.dimension].dimensionName;
-				dim.objectName = dim.dimension;
-
-				filterDimensionNames.push(dim.dimensionName);
-				filterObjectNames.push(dim.objectName);
-
-				for (var j = 0; j < dim.items.length; j++) {
-					items.push(dim.items[j].id);
-				}
-
-				dim.items = items;
-
-				filterItems = filterItems.concat(items);
-			}
-
-			// Axis
-			xLayout.extended.axisDimensions = axisDimensions;
-			xLayout.extended.axisDimensionNames = Ext.Array.unique(axisDimensionNames);
-			xLayout.extended.axisObjectNames = axisObjectNames;
-			xLayout.extended.axisItems = axisItems;
-
-			// Filter
-			xLayout.extended.filterDimensions = filterDimensions;
-			xLayout.extended.filterDimensionNames = Ext.Array.unique(filterDimensionNames);
-			xLayout.extended.filterObjectNames = filterObjectNames;
-			xLayout.extended.filterItems = filterItems;
+		getExtendedLayout: function(layout) {
+			var dimConf = dv.conf.finals.dimension,
+				layout = Ext.clone(layout),
+				xLayout = {
+					columns: [],
+					rows: [],
+					filters: [],
+
+					columnObjectNames: [],
+					columnDimensionNames: [],
+					columnItems: [],
+					columnIds: [],
+					rowObjectNames: [],
+					rowDimensionNames: [],
+					rowItems: [],
+					rowIds: [],
+
+					// Axis
+					axisDimensions: [],
+					axisObjectNames: [],
+					axisDimensionNames: [],
+
+						// For param string
+					sortedAxisDimensionNames: [],
+
+					// Filter
+					filterDimensions: [],
+					filterObjectNames: [],
+					filterDimensionNames: [],
+					filterItems: [],
+					filterIds: [],
+
+						// For param string
+					sortedFilterDimensions: [],
+
+					// All
+					dimensions: [],
+					objectNames: [],
+					dimensionNames: [],
+
+					// Object name maps
+					objectNameDimensionsMap: {},
+					objectNameItemsMap: {},
+					objectNameIdsMap: {},
+
+					// Dimension name maps
+					dimensionNameDimensionsMap: {},
+					dimensionNameItemsMap: {},
+					dimensionNameIdsMap: {},
+
+						// For param string
+					dimensionNameSortedIdsMap: {}
+				};
+
+			Ext.applyIf(xLayout, layout);
+
+			// Columns, rows, filters
+			if (layout.columns) {
+				for (var i = 0, dim, items, xDim; i < layout.columns.length; i++) {
+					dim = layout.columns[i];
+					items = dim.items;
+					xDim = {};
+
+					xDim.dimension = dim.dimension;
+					xDim.objectName = dim.dimension;
+					xDim.dimensionName = dimConf.objectNameMap[dim.dimension].dimensionName;
+
+					if (items) {
+						xDim.items = items;
+						xDim.ids = [];
+
+						for (var j = 0; j < items.length; j++) {
+							xDim.ids.push(items[j].id);
+						}
+					}
+
+					xLayout.columns.push(xDim);
+
+					xLayout.columnObjectNames.push(xDim.objectName);
+					xLayout.columnDimensionNames.push(xDim.dimensionName);
+					xLayout.columnItems = xLayout.columnItems.concat(xDim.items);
+					xLayout.columnIds = xLayout.columnIds.concat(xDim.ids);
+
+					xLayout.axisDimensions.push(xDim);
+					xLayout.axisObjectNames.push(xDim.objectName);
+					xLayout.axisDimensionNames.push(dimConf.objectNameMap[xDim.objectName].dimensionName);
+
+					xLayout.objectNameDimensionsMap[xDim.objectName] = xDim;
+					xLayout.objectNameItemsMap[xDim.objectName] = xDim.items;
+					xLayout.objectNameIdsMap[xDim.objectName] = xDim.ids;
+				}
+			}
+
+			if (layout.rows) {
+				for (var i = 0, dim, items, xDim; i < layout.rows.length; i++) {
+					dim = layout.rows[i];
+					items = dim.items;
+					xDim = {};
+
+					xDim.dimension = dim.dimension;
+					xDim.objectName = dim.dimension;
+					xDim.dimensionName = dimConf.objectNameMap[dim.dimension].dimensionName;
+
+					if (items) {
+						xDim.items = items;
+						xDim.ids = [];
+
+						for (var j = 0; j < items.length; j++) {
+							xDim.ids.push(items[j].id);
+						}
+					}
+
+					xLayout.rows.push(xDim);
+
+					xLayout.rowObjectNames.push(xDim.objectName);
+					xLayout.rowDimensionNames.push(xDim.dimensionName);
+					xLayout.rowItems = xLayout.rowItems.concat(xDim.items);
+					xLayout.rowIds = xLayout.rowIds.concat(xDim.ids);
+
+					xLayout.axisDimensions.push(xDim);
+					xLayout.axisObjectNames.push(xDim.objectName);
+					xLayout.axisDimensionNames.push(dimConf.objectNameMap[xDim.objectName].dimensionName);
+
+					xLayout.objectNameDimensionsMap[xDim.objectName] = xDim;
+					xLayout.objectNameItemsMap[xDim.objectName] = xDim.items;
+					xLayout.objectNameIdsMap[xDim.objectName] = xDim.ids;
+				}
+			}
+
+			if (layout.filters) {
+				for (var i = 0, dim, items, xDim; i < layout.filters.length; i++) {
+					dim = layout.filters[i];
+					items = dim.items;
+					xDim = {};
+
+					xDim.dimension = dim.dimension;
+					xDim.objectName = dim.dimension;
+					xDim.dimensionName = dimConf.objectNameMap[dim.dimension].dimensionName;
+
+					if (items) {
+						xDim.items = items;
+						xDim.ids = [];
+
+						for (var j = 0; j < items.length; j++) {
+							xDim.ids.push(items[j].id);
+						}
+					}
+
+					xLayout.filters.push(xDim);
+
+					xLayout.filterDimensions.push(xDim);
+					xLayout.filterObjectNames.push(xDim.objectName);
+					xLayout.filterDimensionNames.push(dimConf.objectNameMap[xDim.objectName].dimensionName);
+					xLayout.filterItems = xLayout.filterItems.concat(xDim.items);
+					xLayout.filterIds = xLayout.filterIds.concat(xDim.ids);
+
+					xLayout.objectNameDimensionsMap[xDim.objectName] = xDim;
+					xLayout.objectNameItemsMap[xDim.objectName] = xDim.items;
+					xLayout.objectNameIdsMap[xDim.objectName] = xDim.ids;
+				}
+			}
+
+			// Unique dimension names
+			xLayout.axisDimensionNames = Ext.Array.unique(xLayout.axisDimensionNames);
+			xLayout.filterDimensionNames = Ext.Array.unique(xLayout.filterDimensionNames);
+
+			xLayout.columnDimensionNames = Ext.Array.unique(xLayout.columnDimensionNames);
+			xLayout.rowDimensionNames = Ext.Array.unique(xLayout.rowDimensionNames);
+			xLayout.filterDimensionNames = Ext.Array.unique(xLayout.filterDimensionNames);
+
+				// For param string
+			xLayout.sortedAxisDimensionNames = Ext.clone(xLayout.axisDimensionNames).sort();
+			xLayout.sortedFilterDimensions = dv.util.array.sortDimensions(Ext.clone(xLayout.filterDimensions));
 
 			// All
-			xLayout.extended.dimensions = [].concat(axisDimensions, filterDimensions);
-			xLayout.extended.dimensionNames = Ext.Array.unique([].concat(axisDimensionNames, filterDimensionNames));
-			xLayout.extended.objectNames = [].concat(axisObjectNames, filterObjectNames);
-			xLayout.extended.items = [].concat(axisItems, filterItems);
-
-			// Sorted axis
-			xLayout.extended.sortedAxisDimensions = dv.util.array.sortDimensions(Ext.clone(axisDimensions));
-			xLayout.extended.sortedAxisDimensionNames = Ext.Array.unique(Ext.clone(axisDimensionNames).sort());
-			xLayout.extended.sortedAxisObjectNames = Ext.clone(axisObjectNames).sort();
-			xLayout.extended.sortedAxisItems = Ext.clone(axisItems).sort();
-
-			// Sorted filter
-			xLayout.extended.sortedFilterDimensions = dv.util.array.sortDimensions(Ext.clone(filterDimensions));
-			xLayout.extended.sortedFilterDimensionNames = Ext.Array.unique(Ext.clone(filterDimensionNames).sort());
-			xLayout.extended.sortedFilterObjectNames = Ext.clone(filterObjectNames).sort();
-			xLayout.extended.sortedFilterItems = Ext.clone(filterItems).sort();
-
-			// Sorted all
-			xLayout.extended.sortedDimensions = [].concat(xLayout.extended.sortedAxisDimensions, xLayout.extended.sortedFilterDimensions);
-			xLayout.extended.sortedDimensionNames = Ext.Array.unique([].concat(xLayout.extended.sortedAxisDimensionNames, xLayout.extended.sortedFilterDimensionNames));
-			xLayout.extended.sortedObjectNames = [].concat(xLayout.extended.sortedAxisObjectNames, xLayout.extended.sortedFilterObjectNames);
-			xLayout.extended.sortedItems = [].concat(xLayout.extended.sortedAxisItems, xLayout.extended.sortedFilterItems);
-
-			// Maps
-
-			// Add dimensionName keys
-			for (var i = 0, name; i < xLayout.extended.dimensionNames.length; i++) {
-				name = xLayout.extended.dimensionNames[i];
-				dimensionNameItemsMap[name] = [];
-			}
-
-			// Add dimensions and items
-			for (var i = 0, dim; i < xLayout.extended.dimensions.length; i++) {
-				dim = xLayout.extended.dimensions[i];
-
-				// objectName : object
-				objectNameDimensionMap[dim.objectName] = dim;
-
-				// dimensionName : items
-				dimensionNameItemsMap[dim.dimensionName] = dimensionNameItemsMap[dim.dimensionName].concat(Ext.clone(dim.items));
-			}
-
-			xLayout.extended.objectNameDimensionMap = objectNameDimensionMap;
-			xLayout.extended.dimensionNameItemsMap = dimensionNameItemsMap;
-
-			// Columns, rows, filters
-			columns = Ext.clone(xLayout.columns);
-			for (var i = 0, dim; i < columns.length; i++) {
-				dim = columns[i];
-				dim.objectName = dim.dimension;
-				dim.dimensionName = dimConf.objectNameMap[dim.objectName].dimensionName;
-				dim.records = Ext.clone(dim.items);
-				dim.items = xLayout.extended.objectNameDimensionMap[dim.objectName].items;
-
-				objectNameRecordsMap[dim.objectName] = dim.records;
-			}
-
-			rows = Ext.clone(xLayout.rows);
-			for (var i = 0, dim; i < rows.length; i++) {
-				dim = rows[i];
-				dim.objectName = dim.dimension;
-				dim.dimensionName = dimConf.objectNameMap[dim.objectName].dimensionName;
-				dim.records = Ext.clone(dim.items);
-				dim.items = xLayout.extended.objectNameDimensionMap[dim.objectName].items;
-
-				objectNameRecordsMap[dim.objectName] = Ext.clone(dim.records);
-			}
-
-			filters = Ext.clone(xLayout.filters);
-			for (var i = 0, dim; i < filters.length; i++) {
-				dim = filters[i];
-				dim.objectName = dim.dimension;
-				dim.dimensionName = dimConf.objectNameMap[dim.objectName].dimensionName;
-				dim.records = Ext.clone(dim.items);
-				dim.items = xLayout.extended.objectNameDimensionMap[dim.objectName].items;
-
-				objectNameRecordsMap[dim.objectName] = Ext.clone(dim.records);
-			}
-
-			xLayout.extended.columns = columns;
-			xLayout.extended.rows = rows;
-			xLayout.extended.filters = filters;
-
-			xLayout.extended.objectNameRecordsMap = objectNameRecordsMap;
-
-			// columnsDimensionNames, rowsDimensionNames, filtersDimensionNames
-			for (var i = 0; i < xLayout.extended.columns.length; i++) {
-				columnsDimensionNames.push(xLayout.extended.columns[i].dimensionName);
-			}
-
-			for (var i = 0; i < xLayout.extended.rows.length; i++) {
-				rowsDimensionNames.push(xLayout.extended.rows[i].dimensionName);
-			}
-
-			for (var i = 0; i < xLayout.extended.filters.length; i++) {
-				filtersDimensionNames.push(xLayout.extended.filters[i].dimensionName);
-			}
-
-			xLayout.extended.columnsDimensionNames = columnsDimensionNames;
-			xLayout.extended.rowsDimensionNames = rowsDimensionNames;
-			xLayout.extended.filtersDimensionNames = filtersDimensionNames;
+			xLayout.dimensions = [].concat(xLayout.axisDimensions, xLayout.filterDimensions);
+			xLayout.objectNames = [].concat(xLayout.axisObjectNames, xLayout.filterObjectNames);
+			xLayout.dimensionNames = [].concat(xLayout.axisDimensionNames, xLayout.filterDimensionNames);
+
+			// Dimension name maps
+			for (var i = 0, dimName; i < xLayout.dimensionNames.length; i++) {
+				dimName = xLayout.dimensionNames[i];
+
+				xLayout.dimensionNameDimensionsMap[dimName] = [];
+				xLayout.dimensionNameItemsMap[dimName] = [];
+				xLayout.dimensionNameIdsMap[dimName] = [];
+			}
+
+			for (var i = 0, xDim; i < xLayout.dimensions.length; i++) {
+				xDim = xLayout.dimensions[i];
+
+				xLayout.dimensionNameDimensionsMap[xDim.dimensionName].push(xDim);
+				xLayout.dimensionNameItemsMap[xDim.dimensionName] = xLayout.dimensionNameItemsMap[xDim.dimensionName].concat(xDim.items);
+				xLayout.dimensionNameIdsMap[xDim.dimensionName] = xLayout.dimensionNameIdsMap[xDim.dimensionName].concat(xDim.ids);
+			}
+
+				// For param string
+			for (var key in xLayout.dimensionNameIdsMap) {
+				if (xLayout.dimensionNameIdsMap.hasOwnProperty(key)) {
+					xLayout.dimensionNameSortedIdsMap[key] = Ext.clone(xLayout.dimensionNameIdsMap[key]).sort();
+				}
+			}
 
 			return xLayout;
 		},
@@ -607,8 +627,7 @@
 			var dimConf = dv.conf.finals.dimension,
 				getSyncronizedXLayout,
 				getParamString,
-				validateResponse,
-				extendResponse,
+				getExtendedResponse,
 				getDefaultStore,
 				getDefaultNumericAxis,
 				getDefaultCategoryAxis,
@@ -624,23 +643,23 @@
 				initialize;
 
 			getParamString = function(xLayout) {
-				var sortedAxisDimensionNames = xLayout.extended.sortedAxisDimensionNames,
-					sortedFilterDimensions = xLayout.extended.sortedFilterDimensions,
+				var sortedAxisDimensionNames = xLayout.sortedAxisDimensionNames,
+					sortedFilterDimensions = xLayout.sortedFilterDimensions,
+					dimensionNameSortedIdsMap = xLayout.dimensionNameSortedIdsMap,
 					paramString = '?',
 					dimConf = dv.conf.finals.dimension,
 					addCategoryDimension = false,
-					map = xLayout.extended.dimensionNameItemsMap,
-					dx = dimConf.indicator.dimensionName,
-					items;
-
-				for (var i = 0, dimensionName; i < sortedAxisDimensionNames.length; i++) {
-					dimensionName = sortedAxisDimensionNames[i];
-
-					paramString += 'dimension=' + dimensionName;
-
-					items = Ext.clone(xLayout.extended.dimensionNameItemsMap[dimensionName]).sort();
-
-					if (dimensionName === dx) {
+					map = xLayout.dimensionNameItemsMap,
+					dx = dimConf.indicator.dimensionName;
+
+				for (var i = 0, dimName, items; i < sortedAxisDimensionNames.length; i++) {
+					dimName = sortedAxisDimensionNames[i];
+
+					paramString += 'dimension=' + dimName;
+
+					items = Ext.clone(dimensionNameSortedIdsMap[dimName]);
+
+					if (dimName === dx) {
 						for (var j = 0, index; j < items.length; j++) {
 							index = items[j].indexOf('-');
 
@@ -668,7 +687,7 @@
 					for (var i = 0, dim; i < sortedFilterDimensions.length; i++) {
 						dim = sortedFilterDimensions[i];
 
-						paramString += '&filter=' + dim.dimensionName + ':' + dim.items.join(';');
+						paramString += '&filter=' + dim.dimensionName + ':' + dim.ids.join(';');
 					}
 				}
 
@@ -677,42 +696,32 @@
 
 			getSyncronizedXLayout = function(xLayout, response) {
 				var dimensions = [].concat(xLayout.columns, xLayout.rows, xLayout.filters),
-					xOuDimension = xLayout.extended.objectNameDimensionMap[dimConf.organisationUnit.objectName],
+					xOuDimension = xLayout.objectNameDimensionsMap[dimConf.organisationUnit.objectName],
 					isUserOrgunit = xOuDimension && Ext.Array.contains(xOuDimension.items, 'USER_ORGUNIT'),
 					isUserOrgunitChildren = xOuDimension && Ext.Array.contains(xOuDimension.items, 'USER_ORGUNIT_CHILDREN'),
 					peItems = [],
-					ouItems = [];
-
-				// Relative periods
-				for (var i = 0, periodIds; i < dimensions.length; i++) {
-					if (dimensions[i].dimension === dimConf.period.objectName) {
-						periodIds = response.metaData.pe;
-
-						for (var j = 0; j < periodIds.length; j++) {
-							peItems.push({id: periodIds[j]});
-						}
-
-						dimensions[i].items = peItems;
-					}
-				}
-
-				// Add user orgunits
-				if (isUserOrgunit || isUserOrgunitChildren) {
-					if (isUserOrgunit) {
-						ouItems.push(Ext.clone(dv.init.user.ou));
-					}
-					if (isUserOrgunitChildren) {
-						ouItems = ouItems.concat(Ext.clone(dv.init.user.ouc));
-					}
-
-					for (var i = 0; i < dimensions.length; i++) {
-						if (dimensions[i].dimension === dimConf.organisationUnit.objectName) {
-							dimensions[i].items = ouItems;
-						}
-					}
-				}
-
-				return dv.util.chart.extendLayout(xLayout);
+					ouItems = [],
+					layout;
+
+				// Use metaData ids if any
+				for (var i = 0, dim, metaDataDim, items; i < dimensions.length; i++) {
+					dim = dimensions[i];
+					metaDataDim = response.metaData[dim.objectName];
+
+					if (Ext.isArray(metaDataDim)) {
+						items = [];
+
+						for (var j = 0; j < metaDataDim.length; j++) {
+							items.push({id: metaDataDim[j]});
+						}
+
+						dim.items = items;
+					}
+				}
+
+				layout = dv.api.layout.Layout(xLayout);
+
+				return layout ? dv.util.chart.getExtendedLayout(layout) : null;
 			};
 
 			validateResponse = function(response) {
@@ -741,31 +750,35 @@
 				return true;
 			};
 
-			extendResponse = function(response, xLayout) {
+			getExtendedResponse = function(response, xLayout) {
 				response.nameHeaderMap = {};
 				response.idValueMap = {};
+				ids = [];
 
 				var extendHeaders = function() {
 
 					// Extend headers: index, items, size
 					for (var i = 0, header; i < response.headers.length; i++) {
 						header = response.headers[i];
+
+						// Index
 						header.index = i;
 
 						if (header.meta) {
 
-							// Categories
-							if (header.name === dv.conf.finals.dimension.category.dimensionName) {
-								header.items = [].concat(response.metaData[dv.conf.finals.dimension.category.dimensionName]);
-							}
-							// Periods
-							else if (header.name === dv.conf.finals.dimension.period.dimensionName) {
-								header.items = [].concat(response.metaData[dv.conf.finals.dimension.period.dimensionName]);
-							}
+							// Items: get ids from metadata
+							if (Ext.isArray(response.metaData[header.name])) {
+								header.items = Ext.clone(response.metaData[header.name]);
+							}
+							// Items: get ids from xLayout
 							else {
-								header.items = xLayout.extended.dimensionNameItemsMap[header.name];
+								header.items = xLayout.dimensionNameIdsMap[header.name];
 							}
 
+							// Collect ids
+							ids = ids.concat(header.items);
+
+							// Size
 							header.size = header.items.length;
 						}
 					}
@@ -778,10 +791,21 @@
 					}
 				}();
 
+				var extendMetaData = function() {
+					for (var i = 0, id, splitId ; i < ids.length; i++) {
+						id = ids[i];
+
+						if (id.indexOf('-') !== -1) {
+							splitId = id.split('-');
+							response.metaData.names[id] = response.metaData.names[splitId[0]] + ' ' + response.metaData.names[splitId[1]];
+						}
+					}
+				}();
+
 				var createValueIdMap = function() {
 					var valueHeaderIndex = response.nameHeaderMap[dv.conf.finals.dimension.value.value].index,
 						coHeader = response.nameHeaderMap[dv.conf.finals.dimension.category.dimensionName],
-						axisDimensionNames = xLayout.extended.axisDimensionNames,
+						axisDimensionNames = xLayout.axisDimensionNames,
 						idIndexOrder = [];
 
 					// idIndexOrder
@@ -834,12 +858,13 @@
 
 			getDefaultStore = function(xResponse, xLayout) {
 				var pe = dv.conf.finals.dimension.period.dimensionName,
-					columnDimensionName = xLayout.extended.columns[0].dimensionName,
-					rowDimensionName = xLayout.extended.rows[0].dimensionName,
+					columnDimensionName = xLayout.columns[0].dimensionName,
+					rowDimensionName = xLayout.rows[0].dimensionName,
 
 					data = [],
-					columnIds = columnDimensionName === pe ? xResponse.metaData.pe : xLayout.extended.dimensionNameItemsMap[columnDimensionName],
-					rowIds = rowDimensionName === pe ? xResponse.metaData.pe : xLayout.extended.dimensionNameItemsMap[rowDimensionName],
+					//columnIds = columnDimensionName === pe ? xResponse.metaData.pe : xLayout.extended.dimensionNameItemsMap[columnDimensionName],
+					columnIds = Ext.isArray(xResponse.metaData[columnDimensionName]) ? xResponse.metaData[columnDimensionName] : xLayout.dimensionNameIdsMap[columnDimensionName],
+					rowIds = Ext.isArray(xResponse.metaData[rowDimensionName]) ? xResponse.metaData[rowDimensionName] : xLayout.dimensionNameIdsMap[rowDimensionName],
 					trendLineFields = [],
 					targetLineFields = [],
 					baseLineFields = [],
@@ -852,7 +877,9 @@
 
 					obj[dv.conf.finals.data.domain] = xResponse.metaData.names[category];
 					for (var j = 0, id; j < columnIds.length; j++) {
-						id = dv.util.str.replaceAll(columnIds[j], '-', '') + rowIds[i];
+						id = dv.util.str.replaceAll(columnIds[j], '-', '') + dv.util.str.replaceAll(rowIds[i], '-', '');
+						//id = columnIds[j].replace('-', '') + rowIds[i].replace('-', '');
+
 						obj[columnIds[j]] = xResponse.idValueMap[id];
 					}
 
@@ -1029,26 +1056,11 @@
 			};
 
 			getDefaultSeriesTitle = function(store, xResponse) {
-				var a = [],
-					ids;
+				var a = [];
 
-				for (var i = 0, id; i < store.rangeFields.length; i++) {
+				for (var i = 0, id, ids; i < store.rangeFields.length; i++) {
 					id = store.rangeFields[i];
-
-					if (id.indexOf('-') !== -1) {
-						ids = id.split('-');
-						id = '';
-
-						for (var j = 0; j < ids.length; j++) {
-							id += j !== 0 ? ' ' : '';
-							id += xResponse.metaData.names[ids[j]];
-						}
-
-						a.push(id);
-					}
-					else {
-						a.push(xResponse.metaData.names[id]);
-					}
+					a.push(xResponse.metaData.names[id]);
 				}
 
 				return a;
@@ -1225,14 +1237,19 @@
 			};
 
 			getDefaultChartTitle = function(store, xResponse, xLayout) {
-				var filterItems = xLayout.extended.filterItems,
+				var ids = xLayout.filterIds,
 					a = [],
-					text = '';
-
-				if (Ext.isArray(filterItems) && filterItems.length) {
-					for (var i = 0; i < filterItems.length; i++) {
-						text += xResponse.metaData.names[filterItems[i]];
-						text += i < filterItems.length - 1 ? ', ' : '';
+					text = '',
+					fontSize;
+
+				if (xLayout.type === dv.conf.finals.chart.pie) {
+					ids = ids.concat(xLayout.columnIds);
+				}
+
+				if (Ext.isArray(ids) && ids.length) {
+					for (var i = 0; i < ids.length; i++) {
+						text += xResponse.metaData.names[ids[i]];
+						text += i < ids.length - 1 ? ', ' : '';
 					}
 				}
 
@@ -1240,10 +1257,12 @@
 					text = xLayout.title;
 				}
 
+				fontSize = (dv.viewport.centerRegion.getWidth() / text.length) < 11.6 ? 13 : 18;
+
 				return Ext.create('Ext.draw.Sprite', {
 					type: 'text',
 					text: text,
-					font: 'bold 19px ' + dv.conf.chart.style.fontFamily,
+					font: 'bold ' + fontSize + 'px ' + dv.conf.chart.style.fontFamily,
 					fill: '#111',
 					height: 20,
 					y: 	20
@@ -1459,7 +1478,8 @@
 					categoryAxis = getDefaultCategoryAxis(store, xLayout),
 					axes = [numericAxis, categoryAxis],
 					series = [],
-					colors = dv.conf.chart.theme.dv1.slice(0, store.rangeFields.length);
+					colors = dv.conf.chart.theme.dv1.slice(0, store.rangeFields.length),
+					seriesTitles = getDefaultSeriesTitle(store, xResponse);
 
 				// Series
 				for (var i = 0, line; i < store.rangeFields.length; i++) {
@@ -1477,7 +1497,7 @@
 							radius: 4
 						},
 						tips: getDefaultTips(),
-						title: getDefaultSeriesTitle(store, xResponse)
+						title: seriesTitles[i]
 					};
 
 					//if (xLayout.showValues) {
@@ -1596,7 +1616,7 @@
 					chart;
 
 				// Theme
-				colors = dv.conf.chart.theme.dv1.slice(0, xResponse.nameHeaderMap[xLayout.extended.rows[0].dimensionName].items.length);
+				colors = dv.conf.chart.theme.dv1.slice(0, xResponse.nameHeaderMap[xLayout.rowDimensionNames[0]].items.length);
 
 				Ext.chart.theme.dv1 = Ext.extend(Ext.chart.theme.Base, {
 					constructor: function(config) {
@@ -1612,7 +1632,7 @@
 
 				chart.legend.position = 'right';
 				chart.legend.isVertical = true;
-				chart.insetPadding = 20;
+				chart.insetPadding = 40;
 				chart.shadow = true;
 
 				return chart;
@@ -1624,7 +1644,7 @@
 					xResponse,
 					chart;
 
-				xLayout = dv.util.chart.extendLayout(layout);
+				xLayout = dv.util.chart.getExtendedLayout(layout);
 
 				dv.paramString = getParamString(xLayout);
 				url = dv.init.contextPath + '/api/analytics.json' + dv.paramString;
@@ -1665,7 +1685,7 @@
 							return;
 						}
 
-						xResponse = extendResponse(response, xLayout);
+						xResponse = getExtendedResponse(response, xLayout);
 
 						chart = generator[xLayout.type](xResponse, xLayout);
 
@@ -1680,12 +1700,13 @@
 						}
 
 						dv.chart = chart;
+						dv.layout = layout;
 						dv.xLayout = xLayout;
 						dv.xResponse = xResponse;
 
 console.log("xResponse", xResponse);
 console.log("xLayout", xLayout);
-console.log("chart", chart);
+console.log("layout", layout);
 					}
 				});
 
@@ -1706,15 +1727,10 @@
 					alert(r.responseText);
 				},
 				success: function(r) {
-					var layout,
-						xLayout;
-
-					r = Ext.decode(r.responseText);
-					layout = dv.api.Layout(r);
-					xLayout = dv.util.chart.extendLayout(layout);
-
-					if (xLayout) {
-						dv.viewport.setFavorite(xLayout);
+					var layout = dv.api.layout.Layout(Ext.decode(r.responseText));
+
+					if (layout) {
+						dv.viewport.setFavorite(layout);
 					}
 				}
 			});
@@ -1727,481 +1743,313 @@
 DV.core.getApi = function(dv) {
 	var dimConf = dv.conf.finals.dimension,
 		api = {
-			objectNameDimensionClassMap: {}
-		};
-
-	// Dimension
-
-	api.DimensionSuper = function() {
-		return {
-			dimension: null, // string
-
-			items: null // array of records
-		};
-	};
-
-	api.Indicator = function(config) {
-		var indicator = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Indicator config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Indicator dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Indicator items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Indicator has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			indicator.dimension = config.dimension;
-			indicator.dimensionName = dimConf.indicator.dimensionName;
-			indicator.objectName = dimConf.indicator.objectName;
-			indicator.items = Ext.clone(config.items);
-
-			return indicator;
-		}();
-	};
-
-	api.DataElement = function(config) {
-		var dataElement = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Data element config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Data element dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Data element items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Data element has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			dataElement.dimension = config.dimension;
-			dataElement.dimensionName = dimConf.dataElement.dimensionName;
-			dataElement.objectName = dimConf.dataElement.objectName;
-			dataElement.items = Ext.clone(config.items);
-
-			return dataElement;
-		}();
-	};
-
-	api.Operand = function(config) {
-		var operand = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Operand config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Operand dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Operand items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Operand has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			operand.dimension = config.dimension;
-			operand.dimensionName = dimConf.operand.dimensionName;
-			operand.objectName = dimConf.operand.objectName;
-			operand.items = Ext.clone(config.items);
-
-			// Replace operand id characters
-			for (var i = 0, id; i < operand.items.length; i++) {
-				id = operand.items[i].id;
-
-				if (id.indexOf('.') !== -1) {
-					id = id.replace('.', '-');
-					operand.items[i].id = id;
-				}
-			}
-
-			return operand;
-		}();
-	};
-
-	api.DataSet = function(config) {
-		var dataSet = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Data set config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Data set dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Data set items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Data set has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			dataSet.dimension = config.dimension;
-			dataSet.dimensionName = dimConf.dataSet.dimensionName;
-			dataSet.objectName = dimConf.dataSet.objectName;
-			dataSet.items = Ext.clone(config.items);
-
-			return dataSet;
-		}();
-	};
-
-	api.Period = function(config) {
-		var period = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Period config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Period dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Period items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Period has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			period.dimension = config.dimension;
-			period.dimensionName = dimConf.period.dimensionName;
-			period.objectName = dimConf.period.objectName;
-			period.items = Ext.clone(config.items);
-
-			return period;
-		}();
-	};
-
-	api.OrganisationUnit = function(config) {
-		var organisationUnit = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Organisation unit config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Organisation unit dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Organisation unit items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Organisation unit has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			organisationUnit.dimension = config.dimension;
-			organisationUnit.dimensionName = dimConf.organisationUnit.dimensionName;
-			organisationUnit.objectName = dimConf.organisationUnit.objectName;
-			organisationUnit.items = Ext.clone(config.items);
-
-			return organisationUnit;
-		}();
-	};
-
-	api.Dimension = function(config) {
-		var dimension = api.DimensionSuper(),
-			validateConfig;
-
-		validateConfig = function() {
-			if (!Ext.isObject(config)) {
-				alert('Dimension config is not an object');
-				return;
-			}
-
-			if (!Ext.isString(config.dimension)) {
-				alert('Dimension name is illegal');
-				return;
-			}
-
-			if (!Ext.isArray(config.items)) {
-				alert('Dimension items is not an array');
-				return;
-			}
-
-			if (!config.items.length) {
-				alert('Dimension has no items');
-				return;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
+			layout: {
+				Record: null,
+				Dimension: null,
+				Layout: null
+			},
+			response: {
+				Header: null,
+				Response: null
+			}
+		};
+
+	// Layout
+
+	api.layout.Record = function(config) {
+		var record = {};
+
+		// id: string
+
+		return function() {
+			if (!Ext.isObject(config)) {
+				console.log('Record config is not an object: ' + config);
+				return;
+			}
+
+			if (!Ext.isString(config.id)) {
+				alert('Record id is not text: ' + config);
+				return;
+			}
+
+			record.id = config.id.replace('.', '-');
+
+			if (Ext.isString(config.name)) {
+				record.name = config.name;
+			}
+
+			return Ext.clone(record);
+		}();
+	};
+
+	api.layout.Dimension = function(config) {
+		var dimension = {};
+
+		// dimension: string
+
+		// items: [Record]
+
+		return function() {
+			if (!Ext.isObject(config)) {
+				console.log('Dimension config is not an object: ' + config);
+				return;
+			}
+
+			if (!Ext.isString(config.dimension)) {
+				console.log('Dimension name is not text: ' + config);
+				return;
+			}
+
+			if (config.dimension !== dv.conf.finals.dimension.category.objectName) {
+				var records = [];
+
+				if (!Ext.isArray(config.items)) {
+					console.log('Dimension items is not an array: ' + config);
+					return;
+				}
+
+				for (var i = 0; i < config.items.length; i++) {
+					record = api.layout.Record(config.items[i]);
+
+					if (record) {
+						records.push(record);
+					}
+				}
+
+				config.items = records;
+
+				if (!config.items.length) {
+					console.log('Dimension has no valid items: ' + config);
+					return;
+				}
 			}
 
 			dimension.dimension = config.dimension;
-			dimension.dimensionName = config.dimension;
-			dimension.objectName = config.dimension;
-			dimension.items = Ext.clone(config.items);
+			dimension.items = config.items;
 
-			return dimension;
+			return Ext.clone(dimension);
 		}();
 	};
 
-	// Layout
-
-	api.Layout = function(config) {
-		var layout = {
-			type: 'column', // string
-
-			columns: null, // array of {dimension: <objectName>, items: [{id, name, code}]}
-
-			rows: null, // array of {dimension: <objectName>, items: [{id, name, code}]}
-
-			filters: null, // array of {dimension: <objectName>, items: [{id, name, code}]}
-
-			showTrendLine: false, // boolean
-
-			targetLineValue: null, // number
-
-			targetLineTitle: null, // string
-
-			baseLineValue: null, // number
-
-			baseLineTitle: null, // string
-
-			showValues: true, // boolean
-
-			hideLegend: false, // boolean
-
-			hideTitle: false, // boolean
-
-			title: null, // string
-
-			domainAxisTitle: null, // string
-
-			rangeAxisTitle: null, // string
-
-			userOrganisationUnit: false, // boolean
-
-			userOrganisationUnitChildren: false // boolean
+	api.layout.Layout = function(config) {
+		var layout = {};
+
+		// type: string ('column') - 'column', 'stackedColumn', 'bar', 'stackedBar', 'line', 'area', 'pie'
+
+		// columns: [Dimension]
+
+		// rows: [Dimension]
+
+		// filters: [Dimension]
+
+		// showTrendLine: boolean (false)
+
+		// targetLineValue: number
+
+		// targetLineTitle: string
+
+		// baseLineValue: number
+
+		// baseLineTitle: string
+
+		// showValues: boolean (true)
+
+		// hideLegend: boolean (false)
+
+		// hideTitle: boolean (false)
+
+		// domainAxisTitle: string
+
+		// rangeAxisTitle: string
+
+		// userOrganisationUnit: boolean (false)
+
+		// userOrganisationUnitChildren: boolean (false)
+
+		// parentGraphMap: object
+
+		var getValidatedDimensionArray = function(dimensionArray) {
+			var dimensions = [];
+
+			if (!(dimensionArray && Ext.isArray(dimensionArray) && dimensionArray.length)) {
+				return;
+			}
+
+			for (var i = 0, dimension; i < dimensionArray.length; i++) {
+				dimension = api.layout.Dimension(dimensionArray[i]);
+
+				if (dimension) {
+					dimensions.push(dimension);
+				}
+			}
+
+			dimensionArray = dimensions;
+
+			return dimensionArray.length ? dimensionArray : null;
 		};
 
-		var validateConfig = function() {
-			var validateAxis;
-
-			validateAxis = function(axis) {
-				if (!(axis && Ext.isArray(axis) && axis.length)) {
-					return;
-				}
-
-				return true;
-			};
-
+		return function() {
+			var a = [],
+				objectNames = [],
+				dimConf = dv.conf.finals.dimension;
+
+			config.columns = getValidatedDimensionArray(config.columns);
+			config.rows = getValidatedDimensionArray(config.rows);
+			config.filters = getValidatedDimensionArray(config.filters);
+
+			// Config must be an object
 			if (!(config && Ext.isObject(config))) {
-				alert(dv.el + ': Layout config is not an object');
-				return;
-			}
-
-			if (!(validateAxis(config.columns))) {
-				alert(dv.el + ': Columns config is invalid');
-				return;
-			}
-			if (!(validateAxis(config.rows))) {
-				alert(dv.el + ': Rows config is invalid');
-				return;
-			}
-			if (!(validateAxis(config.filters))) {
-				alert(dv.el + ': Filters config is invalid');
-				return;
-			}
-
-			if (!Ext.isBoolean(config.showTrendLine)) {
-				config.showTrendLine = layout.showTrendLine;
-			}
-			if (!Ext.isNumber(config.targetLineValue)) {
-				delete config.targetLineValue;
-			}
-			if (!Ext.isString(config.targetLineTitle) || Ext.isEmpty(config.targetLineTitle)) {
-				delete config.targetLineTitle;
-			}
-			if (!Ext.isNumber(config.baseLineValue)) {
-				delete config.baseLineValue;
-			}
-			if (!Ext.isString(config.baseLineTitle) || Ext.isEmpty(config.baseLineTitle)) {
-				delete config.baseLineTitle;
-			}
-			if (!Ext.isBoolean(config.showValues)) {
-				config.showValues = layout.showValues;
-			}
-			if (!Ext.isBoolean(config.hideLegend)) {
-				config.hideLegend = layout.hideLegend;
-			}
-			if (!Ext.isBoolean(config.hideTitle)) {
-				config.hideTitle = layout.hideTitle;
-			}
-			if (!Ext.isString(config.title) || Ext.isEmpty(config.title)) {
-				delete config.title;
-			}
-			if (!Ext.isString(config.domainAxisTitle) || Ext.isEmpty(config.domainAxisTitle)) {
-				delete config.domainAxisTitle;
-			}
-			if (!Ext.isString(config.rangeAxisTitle) || Ext.isEmpty(config.rangeAxisTitle)) {
-				delete config.rangeAxisTitle;
-			}
-			if (!Ext.isBoolean(config.userOrganisationUnit)) {
-				config.userOrganisationUnit = layout.userOrganisationUnit;
-			}
-			if (!Ext.isBoolean(config.userOrganisationUnitChildren)) {
-				config.userOrganisationUnitChildren = layout.userOrganisationUnitChildren;
-			}
-
-			return true;
-		};
-
-		return function() {
-			if (!validateConfig()) {
-				return;
-			}
-
-			for (var key in config) {
-				if (config.hasOwnProperty(key)) {
-					layout[key] = config[key];
+				console.log(dv.el + ': Layout config is not an object');
+				return;
+			}
+
+			// Series, category, filter
+			if (!config.columns) {
+				alert('No series dimension specified');
+				return;
+			}
+			if (!config.rows) {
+				alert('No category dimension specified');
+				return;
+			}
+			if (!config.filters) {
+				alert('No filter dimensions specified');
+				return;
+			}
+
+			// At least one period specified
+			a = [].concat(config.columns, config.rows, config.filters);
+			for (var i = 0; i < a.length; i++) {
+				if (a[i]) {
+					objectNames.push(a[i].dimension);
 				}
 			}
 
-			// Clint sync
-			if (Ext.isString(layout.targetLineLabel)) {
-				layout.targetLineTitle = layout.targetLineLabel;
-				delete layout.targetLineLabel;
-			}
-			if (Ext.isString(layout.baseLineLabel)) {
-				layout.baseLineTitle = layout.baseLineLabel;
-				delete layout.baseLineLabel;
-			}
-			if (Ext.isString(layout.domainAxisLabel)) {
-				layout.domainAxisTitle = layout.domainAxisLabel;
-				delete layout.domainAxisLabel;
-			}
-			if (Ext.isString(layout.rangeAxisLabel)) {
-				layout.rangeAxisTitle = layout.rangeAxisLabel;
-				delete layout.rangeAxisLabel;
-			}
-			if (Ext.isBoolean(layout.regression)) {
-				layout.showTrendLine = layout.regression;
-				delete layout.regression;
-			}
-			if (Ext.isBoolean(layout.showData)) {
-				layout.showValues = layout.showData;
-				delete layout.showData;
-			}
+			if (!Ext.Array.contains(objectNames, dimConf.period.objectName)) {
+				alert('At least one period must be specified as series, category or filter');
+				return;
+			}
+
+			// Layout
+			layout.type = config.type;
+
+			layout.columns = config.columns;
+			layout.rows = config.rows;
+			layout.filters = config.filters;
+
+			// Properties
+			layout.showTrendLine = Ext.isBoolean(config.regression) ? config.regression : (Ext.isBoolean(config.showTrendLine) ? config.showTrendLine : false);
+			layout.showValues = Ext.isBoolean(config.showData) ? config.showData : (Ext.isBoolean(config.showValues) ? config.showValues : true);
+
+			layout.hideLegend = Ext.isBoolean(config.hideLegend) ? config.hideLegend : false;
+			layout.hideTitle = Ext.isBoolean(config.hideTitle) ? config.hideTitle : false;
+
+			layout.targetLineValue = Ext.isNumber(config.targetLineValue) ? config.targetLineValue : undefined;
+			layout.targetLineTitle = Ext.isString(config.targetLineLabel) && !Ext.isEmpty(config.targetLineLabel) ? config.targetLineLabel :
+				(Ext.isString(config.targetLineTitle) && !Ext.isEmpty(config.targetLineTitle) ? config.targetLineTitle : undefined);
+			layout.baseLineValue = Ext.isNumber(config.baseLineValue) ? config.baseLineValue : undefined;
+			layout.baseLineTitle = Ext.isString(config.baseLineLabel) && !Ext.isEmpty(config.baseLineLabel) ? config.baseLineLabel :
+				(Ext.isString(config.baseLineTitle) && !Ext.isEmpty(config.baseLineTitle) ? config.baseLineTitle : undefined);
+
+			layout.title = Ext.isString(config.title) &&  !Ext.isEmpty(config.title) ? config.title : undefined;
+			layout.domainAxisTitle = Ext.isString(config.domainAxisLabel) && !Ext.isEmpty(config.domainAxisLabel) ? config.domainAxisLabel :
+				(Ext.isString(config.domainAxisTitle) && !Ext.isEmpty(config.domainAxisTitle) ? config.domainAxisTitle : undefined);
+			layout.rangeAxisTitle = Ext.isString(config.rangeAxisLabel) && !Ext.isEmpty(config.rangeAxisLabel) ? config.rangeAxisLabel :
+				(Ext.isString(config.rangeAxisTitle) && !Ext.isEmpty(config.rangeAxisTitle) ? config.rangeAxisTitle : undefined);
+
+			layout.userOrganisationUnit = Ext.isBoolean(config.userOrganisationUnit) ? config.userOrganisationUnit : false;
+			layout.userOrganisationUnitChildren = Ext.isBoolean(config.userOrganisationUnitChildren) ? config.userOrganisationUnitChildren : false;
+
+			layout.parentGraphMap = Ext.isObject(config.parentGraphMap) ? config.parentGraphMap : undefined;
 
 			return Ext.clone(layout);
 		}();
 	};
 
-	api.objectNameDimensionClassMap[dimConf.indicator.objectName] = api.Indicator;
-	api.objectNameDimensionClassMap[dimConf.dataElement.objectName] = api.DataElement;
-	api.objectNameDimensionClassMap[dimConf.operand.objectName] = api.Operand;
-	api.objectNameDimensionClassMap[dimConf.dataSet.objectName] = api.DataSet;
-	api.objectNameDimensionClassMap[dimConf.period.objectName] = api.Period;
-	api.objectNameDimensionClassMap[dimConf.organisationUnit.objectName] = api.OrganisationUnit;
+	// Response
+
+	api.response.Header = function(config) {
+		var header = {};
+
+		// name: string
+
+		// meta: boolean
+
+		return function() {
+			if (!Ext.isObject(config)) {
+				console.log('Header is not an object: ' + config);
+				return;
+			}
+
+			if (!Ext.isString(config.name)) {
+				console.log('Header name is not text: ' + config);
+				return;
+			}
+
+			if (!Ext.isBoolean(config.meta)) {
+				console.log('Header meta is not boolean: ' + config);
+				return;
+			}
+
+			header.name = config.name;
+			header.meta = config.meta;
+
+			return Ext.clone(header);
+		}();
+	};
+
+	api.response.Response = function(config) {
+		var response = {};
+
+		// headers: [Header]
+
+		return function() {
+			var headers = [];
+
+			if (!(config && Ext.isObject(config))) {
+				alert('Data response invalid');
+				return false;
+			}
+
+			if (!(config.headers && Ext.isArray(config.headers))) {
+				alert('Data response invalid');
+				return false;
+			}
+
+			for (var i = 0, header; i < config.headers.length; i++) {
+				header = api.response.Header(config.headers[i]);
+
+				if (header) {
+					headers.push(header);
+				}
+			}
+
+			config.headers = headers;
+
+			if (!config.headers.length) {
+				alert('No valid response headers');
+				return;
+			}
+
+			if (!(Ext.isArray(config.rows) && config.rows.length > 0)) {
+				alert('No values found');
+				return false;
+			}
+
+			if (config.headers.length !== config.rows[0].length) {
+				alert('Data invalid');
+				return false;
+			}
+
+			response.headers = config.headers;
+			response.metaData = config.metaData;
+			response.width = config.width;
+			response.height = config.height;
+			response.rows = config.rows;
+
+			return response;
+		}();
+	};
 
 	return api;
 };

=== modified file 'dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/i18n.vm'
--- dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/i18n.vm	2013-05-13 14:00:23 +0000
+++ dhis-2/dhis-web/dhis-web-visualizer/src/main/webapp/dhis-web-visualizer/i18n.vm	2013-05-23 16:34:57 +0000
@@ -190,5 +190,6 @@
 	can_view: '$encoder.jsEscape($i18n.getString( 'can_view' ) , "'")',
 	can_edit_and_view: '$encoder.jsEscape($i18n.getString( 'can_edit_and_view' ) , "'")',
 	search_for_user_groups: '$encoder.jsEscape($i18n.getString( 'search_for_user_groups' ) , "'")',
-	public_access: '$encoder.jsEscape($i18n.getString( 'public_access' ) , "'")'	
+	public_access: '$encoder.jsEscape($i18n.getString( 'public_access' ) , "'")',
+	detailed_data_elements_cannot_be_specified_as_filter: '$encoder.jsEscape($i18n.getString( 'detailed_data_elements_cannot_be_specified_as_filter' ) , "'")'		
 };