← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 18837: EV range set + layout value dimension implemented.

 

Merge authors:
  Jan Henrik Øverland (janhenrik-overland)
------------------------------------------------------------
revno: 18837 [merge]
committer: Jan Henrik Overland <janhenrik.overland@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2015-04-09 09:54:08 +0200
message:
  EV range set + layout value dimension implemented.
modified:
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/scripts/core.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/styles/style.css
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/i18n/i18n_app.properties
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/app.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/core.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/styles/style.css
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/scripts/app.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/styles/style.css
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/scripts/app.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/styles/style.css


--
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-apps/src/main/webapp/dhis-web-event-reports/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/scripts/core.js	2015-04-01 15:34:05 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/scripts/core.js	2015-04-09 06:29:13 +0000
@@ -314,7 +314,7 @@
 
 				// hideEmptyRows: boolean (false)
 
-                // : boolean (false)
+                // collapseDataDimensions: boolean (false)
 
                 // outputType: string ('EVENT') - 'EVENT', 'TRACKED_ENTITY_INSTANCE', 'ENROLLMENT'
 

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/styles/style.css'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/styles/style.css	2015-03-11 16:35:26 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-reports/styles/style.css	2015-04-08 13:50:25 +0000
@@ -340,11 +340,11 @@
 
 .ns-viewport-text * {
     padding: 3px 10px;
-    font-size: 13px;
+    font-size: 11px;
     color: #515a62;
 }
 .ns-viewport-text h3 {
-    font-size: 15px;
+    font-size: 14px;
     font-weight: 500;
     color: #333;
     padding: 0 0 8px 0;

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/i18n/i18n_app.properties'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/i18n/i18n_app.properties	2015-04-03 15:43:32 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/i18n/i18n_app.properties	2015-04-08 13:50:25 +0000
@@ -180,8 +180,8 @@
 you_do_not_have_access_to_all_items_in_this_favorite=You do not have access to all items in this favorite
 example1=Creating a chart
 example2=Select items from any of the dimensions in the left menu
-example3=Click Layout to arrange your dimensions on table rows and columns
-example4=Click Update to create your table
+example3=Click Layout to arrange your dimensions on chart series and categories
+example4=Click Update to create your chart
 example5=Working with a chart
 example6=Click Options to show trend lines, target lines, axis titles and more
 example7=Click Favorites to save your chart for later use
@@ -200,6 +200,7 @@
 series_dimension=Series dimension
 category_dimension=Category dimension
 chart_filter=Chart filter
+report_filter=Report filter
 value=Value
 average=Average
 count=Count
@@ -214,3 +215,6 @@
 user_sub_units=User sub-units
 user_sub_x2_units=User sub-x2-units
 hide_na_data=Hide n/a data
+duplicate=Duplicate
+remove=Remove
+select=Select

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/app.js	2015-04-03 15:43:32 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/app.js	2015-04-09 07:42:56 +0000
@@ -108,12 +108,18 @@
 
 		// data items
 	(function() {
-        var operatorCmpWidth = 70,
-            valueCmpWidth = 306,
+        var scrollbarWidth = /\bchrome\b/.test(navigator.userAgent.toLowerCase()) ? 8 : 17,
+            nameCmpWidth = 440 - scrollbarWidth,
             buttonCmpWidth = 20,
-            nameCmpWidth = 400,
-            namePadding = '2px 3px',
-            margin = '3px 0 1px';
+            operatorCmpWidth = 70,
+            searchCmpWidth = 70,
+            triggerCmpWidth = 17,
+            valueCmpWidth = 235,
+            rangeSetWidth = 135,
+            namePadding = '3px 3px',
+            margin = '3px 0 1px',
+            removeCmpStyle = 'padding: 0; margin-left: 3px',
+            defaultRangeSetId = 'default';
 
         Ext.define('Ext.ux.panel.DataElementIntegerContainer', {
 			extend: 'Ext.container.Container',
@@ -122,44 +128,96 @@
             bodyStyle: 'border:0 none',
             style: 'margin: ' + margin,
             getRecord: function() {
-                var record = {};
+                var record = {},
+                    isRange = this.rangeSetCmp.getValue() !== defaultRangeSetId;
 
                 record.dimension = this.dataElement.id;
                 record.name = this.dataElement.name;
 
-                if (this.valueCmp.getValue()) {
-					record.filter = this.operatorCmp.getValue() + ':' + this.valueCmp.getValue();
-				}
+                if (isRange) {
+                    record.legendSet = {
+                        id: this.rangeSetCmp.getValue()
+                    };
+
+                    if (this.rangeValueCmp.getValue().length) {
+                        record.filter = 'IN:' + this.rangeValueCmp.getValue().join(';');
+                    }
+                }
+                else {
+                    if (this.valueCmp.getValue()) {
+                        record.filter = this.operatorCmp.getValue() + ':' + this.valueCmp.getValue();
+                    }
+                }
 
 				return record;
             },
             setRecord: function(record) {
-				if (record.filter) {
+                if (Ext.isObject(record.legendSet) && record.legendSet.id) {
+                    this.rangeSetCmp.pendingValue = record.legendSet.id;
+                    this.onRangeSetSelect(record.legendSet.id);
+
+                    if (record.filter) {
+                        var a = record.filter.split(':');
+
+                        if (a.length > 1 && Ext.isString(a[1])) {
+                            this.onRangeSearchSelect(a[1].split(';'), true);
+                        }
+                    }
+                }
+                else if (record.filter) {
+                    this.rangeSetCmp.pendingValue = defaultRangeSetId;
+
 					var a = record.filter.split(':');
 
-					this.operatorCmp.setValue(a[0]);
-					this.valueCmp.setValue(a[1]);
+                    if (a.length > 1) {
+                        this.operatorCmp.setValue(a[0]);
+                        this.valueCmp.setValue(a[1]);
+                    }
+                    else {}
 				}
 			},
             initComponent: function() {
-                var container = this;
+                var container = this,
+                    idProperty = 'id',
+                    nameProperty = 'name',
+                    displayProperty = 'displayName';
 
                 this.nameCmp = Ext.create('Ext.form.Label', {
                     text: this.dataElement.name,
-                    width: nameCmpWidth,
+                    flex: 1,
                     style: 'padding:' + namePadding
                 });
 
+                this.addCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: 'padding: 0',
+                    height: 18,
+                    text: NS.i18n.duplicate,
+                    handler: function() {
+						container.duplicateDataElement();
+					}
+                });
+
+                this.removeCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: removeCmpStyle,
+                    height: 18,
+                    text: NS.i18n.remove,
+                    handler: function() {
+                        container.removeDataElement();
+                    }
+                });
+
                 this.operatorCmp = Ext.create('Ext.form.field.ComboBox', {
-                    valueField: 'id',
-                    displayField: 'name',
+                    valueField: idProperty,
+                    displayField: nameProperty,
                     queryMode: 'local',
                     editable: false,
                     width: operatorCmpWidth,
 					style: 'margin-bottom:0',
                     value: 'EQ',
                     store: {
-                        fields: ['id', 'name'],
+                        fields: [idProperty, nameProperty],
                         data: [
                             {id: 'EQ', name: '='},
                             {id: 'GT', name: '>'},
@@ -172,32 +230,231 @@
                 });
 
                 this.valueCmp = Ext.create('Ext.form.field.Number', {
-                    width: valueCmpWidth,
+                    width: nameCmpWidth - operatorCmpWidth - rangeSetWidth,
 					style: 'margin-bottom:0'
                 });
 
-                this.addCmp = Ext.create('Ext.button.Button', {
-                    text: '+',
-                    width: buttonCmpWidth,
-                    handler: function() {
-						container.duplicateDataElement();
+                this.rangeSearchStore = Ext.create('Ext.data.Store', {
+                    fields: [idProperty, nameProperty]
+                });
+
+                // function
+                this.filterSearchStore = function(isLayout) {
+                    var selected = container.rangeValueCmp.getValue();
+
+                    // hack, using internal method to activate dropdown before filtering
+                    if (isLayout) {
+                        container.rangeSearchCmp.onTriggerClick();
+                        container.rangeSearchCmp.collapse();
+                    }
+
+                    // filter
+                    container.rangeSearchStore.clearFilter();
+
+                    container.rangeSearchStore.filterBy(function(record) {
+                        return !Ext.Array.contains(selected, record.data[idProperty]);
+                    });
+                };
+
+                // function
+                this.onRangeSearchSelect = function(ids, isLayout) {
+                    ids = Ext.Array.from(ids);
+
+                    // store
+                    for (var i = 0, id; i < ids.length; i++) {
+                        id = ids[i];
+
+                        if (container.rangeValueStore.findExact(idProperty, id) === -1) {
+                            container.rangeValueStore.add(container.rangeSearchStore.getAt(container.rangeSearchStore.findExact(idProperty, id)).data);
+                        }
+                    }
+
+                    // search cmp
+                    container.rangeSearchCmp.select([]);
+
+                    // filter
+                    container.filterSearchStore(isLayout);
+                };
+
+                this.rangeSearchCmp = Ext.create('Ext.form.field.ComboBox', {
+                    multiSelect: true,
+                    width: operatorCmpWidth,
+                    style: 'margin-bottom: 0',
+                    emptyText: NS.i18n.select + '..',
+                    valueField: idProperty,
+                    displayField: displayProperty,
+                    editable: false,
+                    queryMode: 'local',
+                    hidden: true,
+                    store: this.rangeSearchStore,
+                    listConfig: {
+                        minWidth: operatorCmpWidth + (nameCmpWidth - operatorCmpWidth - rangeSetWidth)
+                    },
+                    listeners: {
+						select: function() {
+                            container.onRangeSearchSelect(Ext.Array.from(this.getValue())[0]);
+						},
+                        expand: function() {
+                            container.filterSearchStore();
+                        }
 					}
                 });
 
-                this.removeCmp = Ext.create('Ext.button.Button', {
-                    text: 'x',
-                    width: buttonCmpWidth,
-                    handler: function() {
-                        container.removeDataElement();
+                this.rangeValueStore = Ext.create('Ext.data.Store', {
+					fields: [idProperty, nameProperty],
+                    listeners: {
+                        add: function() {
+                            container.rangeValueCmp.select(this.getRange());
+                        },
+                        remove: function() {
+                            container.rangeValueCmp.select(this.getRange());
+                        }
+                    }
+                });
+
+                this.rangeValueCmp = Ext.create('Ext.form.field.ComboBox', {
+                    multiSelect: true,
+                    style: 'margin-bottom: 0',
+                    width: nameCmpWidth - operatorCmpWidth - rangeSetWidth,
+                    valueField: idProperty,
+                    displayField: nameProperty,
+                    emptyText: 'No selected items',
+                    editable: false,
+                    hideTrigger: true,
+                    queryMode: 'local',
+                    hidden: true,
+                    store: container.rangeValueStore,
+                    listConfig: {
+                        minWidth: valueCmpWidth,
+                        cls: 'ns-optionselector'
+                    },
+                    setOptionValues: function(records) {
+                        var me = this;
+
+                        container.rangeValueStore.removeAll();
+                        container.rangeValueStore.loadData(records);
+
+                        me.setValue(records);
+                    },
+					listeners: {
+                        change: function(cmp, newVal, oldVal) {
+                            newVal = Ext.Array.from(newVal);
+                            oldVal = Ext.Array.from(oldVal);
+
+                            if (newVal.length < oldVal.length) {
+                                var id = Ext.Array.difference(oldVal, newVal)[0];
+                                container.rangeValueStore.removeAt(container.rangeValueStore.findExact(idProperty, id));
+                            }
+                        }
+                    }
+                });
+
+                // function
+                this.onRangeSetSelect = function(id) {
+                    if (!id || id === defaultRangeSetId) {
+                        container.operatorCmp.show();
+                        container.valueCmp.show();
+                        container.rangeSearchCmp.hide();
+                        container.rangeValueCmp.hide();
+                    }
+                    else {
+                        var ranges;
+
+                        container.operatorCmp.hide();
+                        container.valueCmp.hide();
+                        container.rangeSearchCmp.show();
+                        container.rangeValueCmp.show();
+
+                        ranges = Ext.clone(ns.core.init.idLegendSetMap[id].legends);
+
+                        // display name
+                        for (var i = 0; i < ranges.length; i++) {
+                            range = ranges[i];
+                            range.displayName = range.name + ' (' + range.startValue + ' - ' + range.endValue + ')';
+                        }
+
+                        container.rangeSearchStore.loadData(ranges);
+                        container.rangeSearchStore.sort('startValue', 'ASC');
+                    }
+                };
+
+                this.rangeSetCmp = Ext.create('Ext.form.field.ComboBox', {
+                    cls: 'ns-combo h22',
+					style: 'margin-bottom: 0',
+                    width: rangeSetWidth,
+                    height: 22,
+                    fieldStyle: 'height: 22px',
+                    queryMode: 'local',
+                    valueField: idProperty,
+                    displayField: nameProperty,
+                    editable: false,
+                    storage: {},
+                    pendingValue: null,
+                    setPendingValue: function() {
+                        if (this.pendingValue) {
+                            this.setValue(this.pendingValue);
+                            container.onRangeSetSelect(this.pendingValue);
+
+                            this.pendingValue = null;
+                        }
+
+                        if (!this.getValue()) {
+                            this.pendingValue = defaultRangeSetId;
+                            this.setPendingValue();
+                        }
+                    },
+                    store: Ext.create('Ext.data.Store', {
+                        fields: [idProperty, nameProperty]
+                    }),
+                    listeners: {
+                        added: function(cb) {
+                            cb.store.add({
+                                id: defaultRangeSetId,
+                                name: 'No range set'
+                            });
+
+                            //cb.setValue(defaultRangeSetId);
+
+                            Ext.Ajax.request({
+                                url: ns.core.init.contextPath + '/api/dataElements/' + container.dataElement.id + '.json?fields=legendSet[id,name]',
+                                success: function(r) {
+                                    r = Ext.decode(r.responseText);
+
+                                    if (Ext.isObject(r) && Ext.isObject(r.legendSet)) {
+                                        cb.store.add(r.legendSet);
+
+                                        cb.setValue(r.legendSet.id);
+                                        container.onRangeSetSelect(r.legendSet.id);
+                                    }
+                                },
+                                callback: function() {
+                                    cb.setPendingValue();
+                                }
+                            });
+                        },
+                        select: function(cb, r) {
+                            var id = Ext.Array.from(r)[0].data.id;
+                            container.onRangeSetSelect(id);
+                        }
                     }
                 });
 
                 this.items = [
-                    this.nameCmp,
+                    {
+                        xtype: 'container',
+                        layout: 'hbox',
+                        width: nameCmpWidth,
+                        items: [
+                            this.nameCmp,
+                            this.addCmp,
+                            this.removeCmp
+                        ]
+                    },
+                    this.rangeSearchCmp,
+                    this.rangeValueCmp,
                     this.operatorCmp,
                     this.valueCmp,
-                    this.addCmp,
-                    this.removeCmp
+                    this.rangeSetCmp
                 ];
 
                 this.callParent();
@@ -231,10 +488,31 @@
 
                 this.nameCmp = Ext.create('Ext.form.Label', {
                     text: this.dataElement.name,
-                    width: nameCmpWidth,
+                    flex: 1,
                     style: 'padding:' + namePadding
                 });
 
+                this.addCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: 'padding: 0',
+                    height: 18,
+                    text: 'Duplicate',
+                    handler: function() {
+						container.duplicateDataElement();
+					}
+                });
+
+                this.removeCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: removeCmpStyle,
+                    height: 18,
+                    text: 'Remove',
+                    handler: function() {
+                        container.removeDataElement();
+                    }
+                });
+
+
                 this.operatorCmp = Ext.create('Ext.form.field.ComboBox', {
                     valueField: 'id',
                     displayField: 'name',
@@ -253,32 +531,23 @@
                 });
 
                 this.valueCmp = Ext.create('Ext.form.field.Text', {
-                    width: valueCmpWidth,
+                    width: nameCmpWidth - operatorCmpWidth,
 					style: 'margin-bottom:0'
                 });
 
-                this.addCmp = Ext.create('Ext.button.Button', {
-                    text: '+',
-                    width: buttonCmpWidth,
-                    handler: function() {
-						container.duplicateDataElement();
-					}
-                });
-
-                this.removeCmp = Ext.create('Ext.button.Button', {
-                    text: 'x',
-                    width: buttonCmpWidth,
-                    handler: function() {
-                        container.removeDataElement();
-                    }
-                });
-
                 this.items = [
-                    this.nameCmp,
+                    {
+                        xtype: 'container',
+                        layout: 'hbox',
+                        width: nameCmpWidth,
+                        items: [
+                            this.nameCmp,
+                            this.addCmp,
+                            this.removeCmp
+                        ]
+                    },
                     this.operatorCmp,
-                    this.valueCmp,
-                    this.addCmp,
-                    this.removeCmp
+                    this.valueCmp
                 ];
 
                 this.callParent();
@@ -316,10 +585,30 @@
 
                 this.nameCmp = Ext.create('Ext.form.Label', {
                     text: this.dataElement.name,
-                    width: nameCmpWidth,
+                    flex: 1,
                     style: 'padding:' + namePadding
                 });
 
+                this.addCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: 'padding: 0',
+                    height: 18,
+                    text: 'Duplicate',
+                    handler: function() {
+						container.duplicateDataElement();
+					}
+                });
+
+                this.removeCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: removeCmpStyle,
+                    height: 18,
+                    text: 'Remove',
+                    handler: function() {
+                        container.removeDataElement();
+                    }
+                });
+
                 this.operatorCmp = Ext.create('Ext.form.field.ComboBox', {
                     valueField: 'id',
                     displayField: 'name',
@@ -342,33 +631,24 @@
                 });
 
                 this.valueCmp = Ext.create('Ext.form.field.Date', {
-					width: valueCmpWidth,
+					width: nameCmpWidth - operatorCmpWidth,
 					style: 'margin-bottom:0',
 					format: 'Y-m-d'
 				});
 
-                this.addCmp = Ext.create('Ext.button.Button', {
-                    text: '+',
-                    width: buttonCmpWidth,
-                    handler: function() {
-						container.duplicateDataElement();
-					}
-                });
-
-                this.removeCmp = Ext.create('Ext.button.Button', {
-                    text: 'x',
-                    width: buttonCmpWidth,
-                    handler: function() {
-                        container.removeDataElement();
-                    }
-                });
-
                 this.items = [
-                    this.nameCmp,
+                    {
+                        xtype: 'container',
+                        layout: 'hbox',
+                        width: nameCmpWidth,
+                        items: [
+                            this.nameCmp,
+                            this.addCmp,
+                            this.removeCmp
+                        ]
+                    },
                     this.operatorCmp,
-                    this.valueCmp,
-                    this.addCmp,
-                    this.removeCmp
+                    this.valueCmp
                 ];
 
                 this.callParent();
@@ -403,48 +683,59 @@
 
                 this.nameCmp = Ext.create('Ext.form.Label', {
                     text: this.dataElement.name,
-                    width: nameCmpWidth,
+                    flex: 1,
                     style: 'padding:' + namePadding
                 });
 
+                this.addCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: 'padding: 0',
+                    height: 18,
+                    text: 'Duplicate',
+                    handler: function() {
+						container.duplicateDataElement();
+					}
+                });
+
+                this.removeCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: removeCmpStyle,
+                    height: 18,
+                    text: 'Remove',
+                    handler: function() {
+                        container.removeDataElement();
+                    }
+                });
+
                 this.valueCmp = Ext.create('Ext.form.field.ComboBox', {
                     valueField: 'id',
                     displayField: 'name',
                     queryMode: 'local',
                     editable: false,
-                    width: operatorCmpWidth + valueCmpWidth,
+                    width: nameCmpWidth,
                     style: 'margin-bottom:0',
                     value: 'true',
                     store: {
                         fields: ['id', 'name'],
                         data: [
-                            {id: 'true', name: EV.i18n.yes},
-                            {id: 'false', name: EV.i18n.no}
+                            {id: 'true', name: ER.i18n.yes},
+                            {id: 'false', name: ER.i18n.no}
                         ]
                     }
                 });
 
-                this.addCmp = Ext.create('Ext.button.Button', {
-                    text: '+',
-                    width: buttonCmpWidth,
-                    handler: function() {
-						container.duplicateDataElement();
-					}
-                });
-
-                this.removeCmp = Ext.create('Ext.button.Button', {
-                    text: 'x',
-                    width: buttonCmpWidth,
-                    handler: function() {
-                        container.removeDataElement();
-                    }
-                });
-
                 this.items = [
-                    this.nameCmp,
-                    this.valueCmp,
-                    this.addCmp,
-                    this.removeCmp
+                    {
+                        xtype: 'container',
+                        layout: 'hbox',
+                        width: nameCmpWidth,
+                        items: [
+                            this.nameCmp,
+                            this.addCmp,
+                            this.removeCmp
+                        ]
+                    },
+                    this.valueCmp
                 ];
 
                 this.callParent();
@@ -457,12 +748,6 @@
 			layout: 'column',
             bodyStyle: 'border:0 none',
             style: 'margin: ' + margin,
-            addCss: function() {
-                var css = '.optionselector .x-boundlist-selected { background-color: #fff; border-color: #fff } \n';
-                css += '.optionselector .x-boundlist-selected.x-boundlist-item-over { background-color: #ddd; border-color: #ddd } \n';
-
-                Ext.util.CSS.createStyleSheet(css);
-            },
             getRecord: function() {
                 var items = this.valueCmp.getValue(),
 					record = {
@@ -507,14 +792,32 @@
                     idProperty = 'code',
                     nameProperty = 'name';
 
-                this.addCss();
-
                 this.nameCmp = Ext.create('Ext.form.Label', {
                     text: this.dataElement.name,
-                    width: nameCmpWidth,
+                    flex: 1,
                     style: 'padding:' + namePadding
                 });
 
+                this.addCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: 'padding: 0',
+                    height: 18,
+                    text: 'Duplicate',
+                    handler: function() {
+						container.duplicateDataElement();
+					}
+                });
+
+                this.removeCmp = Ext.create('Ext.button.Button', {
+                    cls: 'ns-linkbutton',
+                    style: removeCmpStyle,
+                    height: 18,
+                    text: 'Remove',
+                    handler: function() {
+                        container.removeDataElement();
+                    }
+                });
+
                 this.operatorCmp = Ext.create('Ext.form.field.ComboBox', {
                     valueField: 'id',
                     displayField: 'name',
@@ -540,7 +843,7 @@
                         optionSetId = optionSetId || container.dataElement.optionSet.id;
                         pageSize = pageSize || 100;
 
-                        dhis2.ev.store.get('optionSets', optionSetId).done( function(obj) {
+                        dhis2.er.store.get('optionSets', optionSetId).done( function(obj) {
                             if (Ext.isObject(obj) && Ext.isArray(obj.options) && obj.options.length) {
                                 var data = [];
 
@@ -592,7 +895,7 @@
 
                 this.searchCmp = Ext.create('Ext.form.field.ComboBox', {
                     multiSelect: true,
-                    width: 62,
+                    width: operatorCmpWidth - triggerCmpWidth,
                     style: 'margin-bottom:0',
                     emptyText: 'Search..',
                     valueField: idProperty,
@@ -601,7 +904,7 @@
                     enableKeyEvents: true,
                     queryMode: 'local',
                     listConfig: {
-                        minWidth: 346
+                        minWidth: nameCmpWidth - operatorCmpWidth
                     },
                     store: this.searchStore,
                     listeners: {
@@ -643,7 +946,7 @@
                 this.triggerCmp = Ext.create('Ext.button.Button', {
                     cls: 'ns-button-combotrigger',
                     disabledCls: 'ns-button-combotrigger-disabled',
-                    width: 18,
+                    width: triggerCmpWidth,
                     height: 22,
                     handler: function(b) {
                         container.searchStore.loadOptionSet();
@@ -665,7 +968,7 @@
                 this.valueCmp = Ext.create('Ext.form.field.ComboBox', {
                     multiSelect: true,
                     style: 'margin-bottom:0',
-					width: 226,
+					width: nameCmpWidth - operatorCmpWidth - operatorCmpWidth,
                     valueField: idProperty,
                     displayField: nameProperty,
                     emptyText: 'No selected items',
@@ -675,13 +978,13 @@
                     queryMode: 'local',
                     listConfig: {
                         minWidth: 266,
-                        cls: 'optionselector'
+                        cls: 'ns-optionselector'
                     },
                     setOptionValues: function(codeArray) {
                         var me = this,
                             records = [];
 
-                        dhis2.ev.store.get('optionSets', container.dataElement.optionSet.id).done( function(obj) {
+                        dhis2.er.store.get('optionSets', container.dataElement.optionSet.id).done( function(obj) {
                             if (Ext.isObject(obj) && Ext.isArray(obj.options) && obj.options.length) {
                                 records = container.getRecordsByCode(obj.options, codeArray);
 
@@ -705,31 +1008,21 @@
                     }
                 });
 
-                this.addCmp = Ext.create('Ext.button.Button', {
-                    text: '+',
-                    width: buttonCmpWidth,
-                    style: 'font-weight:bold',
-                    handler: function() {
-						container.duplicateDataElement();
-					}
-                });
-
-                this.removeCmp = Ext.create('Ext.button.Button', {
-                    text: 'x',
-                    width: buttonCmpWidth,
-                    handler: function() {
-                        container.removeDataElement();
-                    }
-                });
-
                 this.items = [
-                    this.nameCmp,
+                    {
+                        xtype: 'container',
+                        layout: 'hbox',
+                        width: nameCmpWidth,
+                        items: [
+                            this.nameCmp,
+                            this.addCmp,
+                            this.removeCmp
+                        ]
+                    },
                     this.operatorCmp,
                     this.searchCmp,
                     this.triggerCmp,
-                    this.valueCmp,
-                    this.addCmp,
-                    this.removeCmp
+                    this.valueCmp
                 ];
 
                 this.callParent();
@@ -1006,6 +1299,9 @@
 			filterStore,
             onValueSelect,
 			value,
+            val,
+            onCollapseDataDimensionsChange,
+            collapseDataDimensions,
             aggregationType,
 
 			getStore,
@@ -1030,14 +1326,13 @@
 			dataType = 'aggregated_values',
             defaultValueId = 'default';
 
-		getStore = function(data) {
-			var config = {};
+		getStore = function(applyConfig) {
+			var config = {},
+                store;
 
 			config.fields = ['id', 'name'];
 
-			if (data) {
-				config.data = data;
-			}
+			Ext.apply(config, applyConfig);
 
 			config.getDimensionNames = function() {
 				var dimensionNames = [];
@@ -1049,7 +1344,9 @@
 				return Ext.clone(dimensionNames);
 			};
 
-			return Ext.create('Ext.data.Store', config);
+			store = Ext.create('Ext.data.Store', config);
+
+            return store;
 		};
 
 		getStoreKeys = function(store) {
@@ -1065,11 +1362,11 @@
 			return keys;
 		};
 
-		colStore = getStore();
-		rowStore = getStore();
-        fixedFilterStore = getStore();
-        filterStore = getStore();
-        valueStore = getStore();
+		colStore = getStore({name: 'colStore'});
+		rowStore = getStore({name: 'rowStore'});
+        fixedFilterStore = getStore({name: 'fixedFilterStore'});
+        filterStore = getStore({name: 'filterStore'});
+        valueStore = getStore({name: 'valueStore'});
 
         // store functions
         valueStore.addDefaultData = function() {
@@ -1204,7 +1501,7 @@
 				height: 25,
 				items: {
 					xtype: 'label',
-					text: NS.i18n.chart_filter,
+					text: NS.i18n.report_filter,
 					cls: 'ns-toolbar-multiselect-leftright-label'
 				}
 			},
@@ -1230,7 +1527,6 @@
 			store: filterStore,
 			listeners: {
 				afterrender: function(ms) {
-
 					ms.store.on('add', function() {
 						Ext.defer( function() {
 							ms.boundList.getSelectionModel().deselectAll();
@@ -1242,7 +1538,7 @@
 
         aggregationType = Ext.create('Ext.form.field.ComboBox', {
 			cls: 'ns-combo h22',
-			width: 70,
+			width: 80,
 			height: 22,
 			style: 'margin: 0',
             fieldStyle: 'height: 22px',
@@ -1263,15 +1559,7 @@
             },
 			store: Ext.create('Ext.data.Store', {
 				fields: ['id', 'text'],
-				data: [
-					{id: 'COUNT', text: NS.i18n.count},
-					{id: 'AVERAGE', text: NS.i18n.average},
-					{id: 'SUM', text: NS.i18n.sum},
-					{id: 'STDDEV', text: NS.i18n.stddev},
-					{id: 'VARIANCE', text: NS.i18n.variance},
-					{id: 'MIN', text: NS.i18n.min},
-					{id: 'MAX', text: NS.i18n.max}
-				]
+				data: ns.core.conf.aggregationType.data
 			}),
             resetData: function() {
                 this.setDisabled();
@@ -1287,7 +1575,11 @@
 
                 // remove ux and layout item
                 if (hasDimension(id, valueStore)) {
-                    ns.app.accordion.getUx(id).removeDataElement();
+                    var uxArray = ns.app.accordion.getUxArray(id);
+
+                    for (var i = 0; i < uxArray.length; i++) {
+                        uxArray[i].removeDataElement();
+                    }
                 }
             }
         };
@@ -1325,6 +1617,42 @@
             }
 		});
 
+        val = Ext.create('Ext.panel.Panel', {
+            bodyStyle: 'padding: 1px',
+            width: defaultWidth,
+            height: 220,
+            items: value,
+            tbar: {
+                height: 25,
+                style: 'padding: 1px',
+                items: [
+                    {
+                        xtype: 'label',
+                        height: 22,
+                        style: 'padding-left: 6px; line-height: 22px',
+                        text: NS.i18n.value
+                    },
+                    '->',
+                    aggregationType
+                ]
+            }
+        });
+
+        onCollapseDataDimensionsChange = function(value) {
+            toggleDataItems(value);
+            toggleValueGui(value);
+        };
+
+        collapseDataDimensions = Ext.create('Ext.form.field.Checkbox', {
+            boxLabel: NS.i18n.collapse_data_dimensions,
+            style: 'margin-left: 3px',
+            listeners: {
+                change: function(chb, value) {
+                    onCollapseDataDimensionsChange(value);
+                }
+            }
+        });
+
 		selectPanel = Ext.create('Ext.panel.Panel', {
 			bodyStyle: 'border:0 none',
 			items: [
@@ -1350,37 +1678,25 @@
 					bodyStyle: 'border:0 none',
 					items: [
 						row,
-                        {
-                            xtype: 'panel',
-                            bodyStyle: 'padding: 1px',
-                            width: defaultWidth,
-                            height: 220,
-                            items: value,
-                            tbar: {
-                                height: 25,
-                                style: 'padding: 1px',
-                                items: [
-                                    {
-                                        xtype: 'label',
-                                        height: 22,
-                                        style: 'padding-left: 6px; line-height: 22px',
-                                        text: NS.i18n.value
-                                    },
-                                    '->',
-                                    aggregationType
-                                ]
-                            }
-                        }
+                        val
 					]
 				}
 			]
 		});
 
-        addDimension = function(record, store, excludedStores) {
-            var store = dimensionStoreMap[record.id] || store || filterStore;
+        addDimension = function(record, store, excludedStores, force) {
+            store = store && force ? store : dimensionStoreMap[record.id] || store || filterStore;
 
-            if (!hasDimension(record.id, excludedStores) && record.id !== value.getValue()) {
-                store.add(record);
+            if (hasDimension(record.id, excludedStores)) {
+                if (force) {
+                    removeDimension(record.id);
+                    store.add(record);
+                }
+            }
+            else {
+                if (record.id !== value.getValue()) {
+                    store.add(record);
+                }
             }
         };
 
@@ -1432,6 +1748,10 @@
                 map[record.data.id] = fixedFilterStore;
             });
 
+            //valueStore.each(function(record) {
+                //map[record.data.id] = valueStore;
+            //});
+
             return map;
         };
 
@@ -1462,6 +1782,57 @@
 			fixedFilterStore.setListHeight();
 		};
 
+        toggleDataItems = function(param) {
+            var stores = [colStore, rowStore, filterStore, fixedFilterStore],
+                collapse = Ext.isObject(param) && param.collapseDataItems ? param.collapseDataItems : param,
+                keys = ['ou', 'pe', 'dates'],
+                dy = ['dy'],
+                keys;
+
+            // clear filters
+            for (var i = 0, store; i < stores.length; i++) {
+                stores[i].clearFilter();
+            }
+
+            // add dy if it does not exist
+            if (!hasDimension('dy')) {
+                addDimension({
+                    id: 'dy',
+                    name: NS.i18n.data
+                }, rowStore);
+            }
+
+            // keys
+            if (collapse) { // included keys
+                keys = ['ou', 'pe', 'dates', 'dy'];
+            }
+            else { // excluded keys
+                keys = ['dy'];
+            }
+
+            // data items
+            for (var i = 0, store, include; i < stores.length; i++) {
+                store = stores[i];
+
+                if (collapse) {
+                    store.filterBy(function(record, id) {
+                        return Ext.Array.contains(keys, record.data.id);
+                    });
+                }
+                else {
+                    store.filterBy(function(record, id) {
+                        return !Ext.Array.contains(keys, record.data.id);
+                    });
+                }
+            }
+        };
+
+        toggleValueGui = function(param) {
+            var collapse = Ext.isObject(param) && param.collapseDataItems ? param.collapseDataItems : param;
+
+            val.setDisabled(collapse);
+        };
+
 		window = Ext.create('Ext.window.Window', {
 			title: NS.i18n.table_layout,
 			bodyStyle: 'background-color:#fff; padding:' + margin + 'px',
@@ -1469,6 +1840,7 @@
 			autoShow: true,
 			modal: true,
 			resizable: false,
+			dataType: dataType,
 			colStore: colStore,
 			rowStore: rowStore,
             fixedFilterStore: fixedFilterStore,
@@ -1478,9 +1850,14 @@
             addDimension: addDimension,
             removeDimension: removeDimension,
             hasDimension: hasDimension,
+            dimensionStoreMap: dimensionStoreMap,
             saveState: saveState,
             resetData: resetData,
             reset: reset,
+            onCollapseDataDimensionsChange: onCollapseDataDimensionsChange,
+            collapseDataDimensions: collapseDataDimensions,
+            toggleDataItems: toggleDataItems,
+            toggleValueGui: toggleValueGui,
             getValueConfig: function() {
                 var config = {},
                     valueId = value.getValue();
@@ -1492,23 +1869,42 @@
 
                 return config;
             },
-			hideOnBlur: true,
+            getOptions: function() {
+                return {
+                    collapseDataDimensions: collapseDataDimensions.getValue()
+                };
+            },
+            hideOnBlur: true,
 			items: selectPanel,
 			bbar: [
 				'->',
 				{
 					text: NS.i18n.hide,
-                    handler: function() {
-                        window.hide();
-                    }
+					listeners: {
+						added: function(b) {
+							b.on('click', function() {
+								window.hide();
+							});
+						}
+					}
 				},
 				{
 					text: '<b>' + NS.i18n.update + '</b>',
-                    handler: function() {
-                        ns.app.viewport.update();
-
-                        window.hide();
-                    }
+					listeners: {
+						added: function(b) {
+							b.on('click', function() {
+								var config = ns.core.web.report.getLayoutConfig();
+
+								if (!config) {
+									return;
+								}
+
+								ns.core.web.report.getData(config, false);
+
+								window.hide();
+							});
+						}
+					}
 				}
 			],
 			listeners: {
@@ -3108,8 +3504,10 @@
 
             baseWidth = 446,
             toolWidth = 36,
+            accBaseWidth = baseWidth - 2,
 
-            accBaseWidth = baseWidth - 2;
+            conf = ns.core.conf,
+            rp = conf.period.relativePeriods;
 
 		// stores
 
@@ -3227,14 +3625,13 @@
 				levels = [],
 				groups = [],
 
-				optionsWindow = ns.app.aggregateOptionsWindow;
+                optionsWindow = ns.app.aggregateOptionsWindow;
 
             reset();
 
+            //ns.app.typeToolbar.setType(layout.dataType);
             ns.app.aggregateLayoutWindow.reset();
-
-            // type
-            ns.app.viewport.chartType.setChartType(layout.type);
+            //ns.app.queryLayoutWindow.reset();
 
 			// data
             programStore.add(layout.program);
@@ -3373,10 +3770,12 @@
             var load;
 
             programId = layout ? layout.program.id : programId;
+
+            // reset
 			stage.clearValue();
-
 			dataElementsByStageStore.removeAll();
 			dataElementSelected.removeAllDataElements(true);
+            ns.app.aggregateLayoutWindow.value.resetData();
 
             load = function(stages) {
                 stage.enable();
@@ -3623,16 +4022,16 @@
 
 				return hasDataElement;
 			},
-            getUxById: function(dataElementId) {
-                var ux;
+            getUxArrayById: function(dataElementId) {
+                var uxArray = [];
 
                 this.items.each(function(item) {
 					if (item.dataElement.id === dataElementId) {
-						ux = item;
+						uxArray.push(item);
 					}
 				});
 
-                return ux;
+                return uxArray;
             },
 			removeAllDataElements: function(reset) {
 				var items = this.items.items,
@@ -3652,6 +4051,7 @@
 			index = index || dataElementSelected.items.items.length;
 
 			getUxType = function(element) {
+
 				if (Ext.isObject(element.optionSet) && Ext.isString(element.optionSet.id)) {
 					return 'Ext.ux.panel.OrganisationUnitGroupSetContainer';
 				}
@@ -3690,6 +4090,7 @@
                     }
 
                     ns.app.aggregateLayoutWindow.removeDimension(element.id, ns.app.aggregateLayoutWindow.valueStore);
+                    //ns.app.queryLayoutWindow.removeDimension(element.id);
 				}
 			};
 
@@ -3706,8 +4107,8 @@
         selectDataElements = function(items, layout) {
             var dataElements = [],
 				allElements = [],
-                fixedFilterElementIds = [],
                 aggWindow = ns.app.aggregateLayoutWindow,
+                //queryWindow = ns.app.queryLayoutWindow,
                 includeKeys = ['int', 'number', 'bool', 'boolean', 'trueOnly'],
                 ignoreKeys = ['pe', 'ou'],
                 recordMap = {
@@ -3715,14 +4116,15 @@
 					'ou': {id: 'ou', name: 'Organisation units'}
 				},
                 extendDim = function(dim) {
+                    var md = ns.app.response.metaData,
+                        dimConf = ns.core.conf.finals.dimension;
+
                     dim.id = dim.id || dim.dimension;
-                    dim.name = dim.name || ns.app.response.metaData.names[dim.dimension];
+                    dim.name = dim.name || md.names[dim.dimension] || dimConf.objectNameMap[dim.dimension].name;
 
                     return dim;
                 };
 
-                fixedFilterElementIds = [];
-
 			// data element objects
             for (var i = 0, item; i < items.length; i++) {
 				item = items[i];
@@ -3771,7 +4173,7 @@
                 element.name = element.name || element.displayName;
                 recordMap[element.id] = element;
 
-                // add ux if not selected as value
+                // dont add ux if dim is selected as value
                 if (element.id !== aggWindow.value.getValue()) {
                     ux = addUxFromDataElement(element);
 
@@ -3780,49 +4182,54 @@
                     }
                 }
 
-                store = Ext.Array.contains(includeKeys, element.type) || element.optionSet ? aggWindow.rowStore : aggWindow.fixedFilterStore;
-
-                if (store === aggWindow.fixedFilterStore) {
-					fixedFilterElementIds.push(element.id);
-				}
+                store = Ext.Array.contains(includeKeys, element.type) || element.optionSet ? aggWindow.colStore : aggWindow.fixedFilterStore;
 
                 aggWindow.addDimension(element, store, valueStore);
+                //queryWindow.colStore.add(element);
 			}
 
-			if (layout) { // && layout.dataType === 'aggregated_values') {
-                aggWindow.reset(true);
-
-                if (layout.startDate && layout.endDate) {
-                    aggWindow.fixedFilterStore.add({id: dimConf.startEndDate.value, name: dimConf.startEndDate.name});
-                }
-
-                if (layout.columns) {
+            // favorite
+			if (layout && layout.dataType === 'aggregated_values') {
+
+                // start end dates
+				if (layout.startDate && layout.endDate) {
+					aggWindow.fixedFilterStore.add({id: dimConf.startEndDate.value, name: dimConf.startEndDate.name});
+				}
+
+                // columns
+				if (layout.columns) {
 					for (var i = 0, record, dim; i < layout.columns.length; i++) {
                         dim = layout.columns[i];
                         record = recordMap[dim.dimension];
 
-						aggWindow.colStore.add(record || extendDim(Ext.clone(dim)));
+						aggWindow.addDimension(record || extendDim(Ext.clone(dim)), aggWindow.colStore, null, true);
 					}
 				}
 
-                if (layout.rows) {
+                // rows
+				if (layout.rows) {
 					for (var i = 0, record, dim; i < layout.rows.length; i++) {
                         dim = layout.rows[i];
                         record = recordMap[dim.dimension];
 
-						aggWindow.rowStore.add(record || extendDim(Ext.clone(dim)));
+						aggWindow.addDimension(record || extendDim(Ext.clone(dim)), aggWindow.rowStore, null, true);
 					}
 				}
 
-                if (layout.filters) {
+                // filters
+				if (layout.filters) {
 					for (var i = 0, store, record, dim; i < layout.filters.length; i++) {
                         dim = layout.filters[i];
 						record = recordMap[dim.dimension];
 						store = Ext.Array.contains(includeKeys, element.type) || element.optionSet ? aggWindow.filterStore : aggWindow.fixedFilterStore;
 
-						store.add(record || extendDim(Ext.clone(dim)));
+                        aggWindow.addDimension(record || extendDim(Ext.clone(dim)), store, null, true);
 					}
 				}
+
+                // collapse data dimensions
+                aggWindow.collapseDataDimensions.setValue(layout.collapseDataDimensions);
+                aggWindow.onCollapseDataDimensionsChange(layout.collapseDataDimensions);
 			}
         };
 
@@ -5414,6 +5821,9 @@
                 return;
             }
 
+            // dy
+            map['dy'] = [{dimension: 'dy'}];
+
 			// pe
             if (periodMode.getValue() === 'dates') {
                 view.startDate = startDate.getSubmitValue();
@@ -5459,110 +5869,71 @@
             //map['longitude'] = [{dimension: 'longitude'}];
             //map['latitude'] = [{dimension: 'latitude'}];
 
-            // dimensions
-            if (layoutWindow.colStore) {
-				layoutWindow.colStore.each(function(item) {
-					a = map[item.data.id] || [];
-
-					if (a.length) {
-						if (a.length === 1) {
-							columns.push(a[0]);
-						}
-						else {
-							var dim;
-
-							for (var i = 0; i < a.length; i++) {
-								if (!dim) {
-									dim = a[i];
-								}
-								else {
-									dim.filter += ':' + a[i].filter;
-								}
-							}
-
-							columns.push(dim);
-						}
-					}
-				});
-			}
-
-            if (layoutWindow.rowStore) {
-				layoutWindow.rowStore.each(function(item) {
-					a = map[item.data.id] || [];
-
-					if (a.length) {
-						if (a.length === 1) {
-							rows.push(a[0]);
-						}
-						else {
-							var dim;
-
-							for (var i = 0; i < a.length; i++) {
-								if (!dim) {
-									dim = a[i];
-								}
-								else {
-									dim.filter += ':' + a[i].filter;
-								}
-							}
-
-							rows.push(dim);
-						}
-					}
-				});
-			}
-
-            if (layoutWindow.filterStore) {
-				layoutWindow.filterStore.each(function(item) {
-					a = map[item.data.id] || [];
-
-					if (a.length) {
-						if (a.length === 1) {
-							filters.push(a[0]);
-						}
-						else {
-							var dim;
-
-							for (var i = 0; i < a.length; i++) {
-								if (!dim) {
-									dim = a[i];
-								}
-								else {
-									dim.filter += ':' + a[i].filter;
-								}
-							}
-
-							filters.push(dim);
-						}
-					}
-				});
-			}
-
-            if (layoutWindow.fixedFilterStore) {
-				layoutWindow.fixedFilterStore.each(function(item) {
-					a = map[item.data.id] || [];
-
-					if (a.length) {
-						if (a.length === 1) {
-							filters.push(a[0]);
-						}
-						else {
-							var dim;
-
-							for (var i = 0; i < a.length; i++) {
-								if (!dim) {
-									dim = a[i];
-								}
-								else {
-									dim.filter += ':' + a[i].filter;
-								}
-							}
-
-							filters.push(dim);
-						}
-					}
-				});
-			}
+            addAxisDimension = function(a, axis) {
+                if (a.length) {
+                    if (a.length === 1) {
+                        axis.push(a[0]);
+                    }
+                    else {
+                        var dim;
+
+                        for (var i = 0; i < a.length; i++) {
+                            if (!dim) { //todo ??
+                                dim = a[i];
+                            }
+                            else {
+                                dim.filter += ':' + a[i].filter;
+                            }
+                        }
+
+                        axis.push(dim);
+                    }
+                }
+            };
+
+            // columns
+            store = layoutWindow.colStore;
+
+            if (store) {
+                data = store.snapshot || store.data;
+
+                data.each(function(item) {
+                    addAxisDimension(map[item.data.id] || [], columns);
+                });
+            }
+
+            // rows
+            store = layoutWindow.rowStore;
+
+            if (store) {
+                data = store.snapshot || store.data;
+
+                data.each(function(item) {
+                    addAxisDimension(map[item.data.id] || [], rows);
+                });
+            }
+
+            // filters
+            store = layoutWindow.filterStore;
+
+            if (store) {
+                data = store.snapshot || store.data;
+
+                data.each(function(item) {
+                    addAxisDimension(map[item.data.id] || [], filters);
+                });
+            }
+
+            // fixed filters
+            store = layoutWindow.fixedFilterStore;
+
+            if (store) {
+                data = store.snapshot || store.data;
+
+                data.each(function(item) {
+                    addAxisDimension(map[item.data.id] || [], filters);
+                });
+            }
 
 			if (columns.length) {
 				view.columns = columns;
@@ -5644,6 +6015,10 @@
 			setGui: setGui,
 			getView: getView,
 
+            getUxArray: function(id) {
+                return dataElementSelected.getUxArrayById(id);
+            },
+
             listeners: {
                 added: function() {
 					ns.app.accordion = this;
@@ -7157,18 +7532,17 @@
 					}
                 },
 				afterrender: function(p) {
-					var liStyle = 'padding:3px 10px; color:#333',
-						html = '';
+					var html = '';
 
-					html += '<div style="padding:20px">';
-					html += '<div style="font-size:14px; padding-bottom:8px">' + NS.i18n.example1 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example2 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example3 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example4 + '</div>';
-					html += '<div style="font-size:14px; padding-top:20px; padding-bottom:8px">' + NS.i18n.example5 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example6 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example7 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example8 + '</div>';
+					html += '<div class="ns-viewport-text" style="padding:20px">';
+					html += '<h3>' + NS.i18n.example1 + '</h3>';
+					html += '<div>- ' + NS.i18n.example2 + '</div>';
+					html += '<div>- ' + NS.i18n.example3 + '</div>';
+					html += '<div>- ' + NS.i18n.example4 + '</div>';
+					html += '<h3 style="padding-top:20px">' + NS.i18n.example5 + '</h3>';
+					html += '<div>- ' + NS.i18n.example6 + '</div>';
+					html += '<div>- ' + NS.i18n.example7 + '</div>';
+					html += '<div>- ' + NS.i18n.example8 + '</div>';
 					html += '</div>';
 
 					p.update(html);
@@ -7470,6 +7844,15 @@
                                             }
                                         });
 
+                                        // legend sets
+                                        requests.push({
+                                            url: contextPath + '/api/legendSets.json?fields=id,name,legends[id,name,startValue,endValue,color]&paging=false',
+                                            success: function(r) {
+                                                init.legendSets = Ext.decode(r.responseText).legendSets || [];
+                                                fn();
+                                            }
+                                        });
+
                                         // dimensions
                                         requests.push({
                                             url: init.contextPath + '/api/organisationUnitGroupSets.json?fields=id,' + namePropertyUrl + '&paging=false',

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/core.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/core.js	2015-04-03 15:43:32 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/scripts/core.js	2015-04-09 07:52:01 +0000
@@ -3,16 +3,509 @@
 	// ext config
 	Ext.Ajax.method = 'GET';
 
+    Ext.isIE = (/trident/.test(Ext.userAgent));
+
+    Ext.isIE11 = Ext.isIE && (/rv:11.0/.test(Ext.userAgent));
+
+    Ext.util.CSS.createStyleSheet = function(cssText, id) {
+        var ss,
+            head = document.getElementsByTagName("head")[0],
+            styleEl = document.createElement("style");
+
+        styleEl.setAttribute("type", "text/css");
+
+        if (id) {
+           styleEl.setAttribute("id", id);
+        }
+
+        if (Ext.isIE && !Ext.isIE11) {
+           head.appendChild(styleEl);
+           ss = styleEl.styleSheet;
+           ss.cssText = cssText;
+        }
+        else {
+            try {
+                styleEl.appendChild(document.createTextNode(cssText));
+            }
+            catch(e) {
+               styleEl.cssText = cssText;
+            }
+            head.appendChild(styleEl);
+            ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || document.styleSheets[document.styleSheets.length-1]);
+        }
+        this.cacheStyleSheet(ss);
+        return ss;
+    };
+
     // override
+    Ext.override(Ext.chart.Chart, {
+        insetPaddingObject: {},
+
+        alignAxes: function() {
+            var me = this,
+                axes = me.axes,
+                legend = me.legend,
+                edges = ['top', 'right', 'bottom', 'left'],
+                chartBBox,
+                insetPadding = me.insetPadding,
+                insetPaddingObject = me.insetPaddingObject,
+                insets = {
+                    top: insetPaddingObject.top || insetPadding,
+                    right: insetPaddingObject.right || insetPadding,
+                    bottom: insetPaddingObject.bottom || insetPadding,
+                    left: insetPaddingObject.left || insetPadding
+                };
+
+            function getAxis(edge) {
+                var i = axes.findIndex('position', edge);
+                return (i < 0) ? null : axes.getAt(i);
+            }
+
+
+            Ext.each(edges, function(edge) {
+                var isVertical = (edge === 'left' || edge === 'right'),
+                    axis = getAxis(edge),
+                    bbox;
+
+
+                if (legend !== false) {
+                    if (legend.position === edge) {
+                        bbox = legend.getBBox();
+                        insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
+                    }
+                }
+
+
+
+                if (axis && axis.bbox) {
+                    bbox = axis.bbox;
+                    insets[edge] += (isVertical ? bbox.width : bbox.height);
+                }
+            });
+
+            chartBBox = {
+                x: insets.left,
+                y: insets.top,
+                width: me.curWidth - insets.left - insets.right,
+                height: me.curHeight - insets.top - insets.bottom
+            };
+            me.chartBBox = chartBBox;
+
+
+
+            axes.each(function(axis) {
+                var pos = axis.position,
+                    isVertical = (pos === 'left' || pos === 'right');
+
+                axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
+                axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
+                axis.width = (isVertical ? chartBBox.width : chartBBox.height);
+                axis.length = (isVertical ? chartBBox.height : chartBBox.width);
+            });
+        }
+    });
+
     Ext.override(Ext.chart.series.Line, {
         drawSeries: function() {
             var ak=this,au=ak.chart,S=au.axes,ao=au.getChartStore(),V=ao.getCount(),u=ak.chart.surface,am={},R=ak.group,K=ak.showMarkers,aA=ak.markerGroup,D=au.shadow,C=ak.shadowGroups,X=ak.shadowAttributes,O=ak.smooth,q=C.length,ar=["M"],T=["M"],d=["M"],b=["M"],J=au.markerIndex,ai=[].concat(ak.axis),ah,av=[],ag={},aa=[],v={},I=false,Q=[],az=ak.markerStyle,Z=ak.style,t=ak.colorArrayStyle,P=t&&t.length||0,L=Ext.isNumber,aw=ak.seriesIdx,g=ak.getAxesForXAndYFields(),l=g.xAxis,ay=g.yAxis,ac,h,ab,ad,A,c,ae,H,G,f,e,s,r,W,N,M,at,m,F,E,aB,n,p,B,a,Y,af,z,aq,w,ap,o,ax,an,al,U,k,aj;if(ak.fireEvent("beforedraw",ak)===false){return}if(!V||ak.seriesIsHidden){aj=this.items;if(aj){for(N=0,at=aj.length;N<at;++N){if(aj[N].sprite){aj[N].sprite.hide(true)}}}return}an=Ext.apply(az||{},ak.markerConfig);U=an.type;delete an.type;al=Z;if(!al["stroke-width"]){al["stroke-width"]=0.5}if(J&&aA&&aA.getCount()){for(N=0;N<J;N++){E=aA.getAt(N);aA.remove(E);aA.add(E);aB=aA.getAt(aA.getCount()-2);E.setAttributes({x:0,y:0,translate:{x:aB.attr.translation.x,y:aB.attr.translation.y}},true)}}ak.unHighlightItem();ak.cleanHighlights();ak.setBBox();am=ak.bbox;ak.clipRect=[am.x,am.y,am.width,am.height];for(N=0,at=ai.length;N<at;N++){m=S.get(ai[N]);if(m){F=m.calcEnds();if(m.position=="top"||m.position=="bottom"){z=F.from;aq=F.to}else{w=F.from;ap=F.to}}}if(ak.xField&&!L(z)&&(l=="bottom"||l=="top")&&!S.get(l)){m=Ext.create("Ext.chart.axis.Axis",{chart:au,fields:[].concat(ak.xField)}).calcEnds();z=m.from;aq=m.to}if(ak.yField&&!L(w)&&(ay=="right"||ay=="left")&&!S.get(ay)){m=Ext.create("Ext.chart.axis.Axis",{chart:au,fields:[].concat(ak.yField)}).calcEnds();w=m.from;ap=m.to}if(isNaN(z)){z=0;Y=am.width/((V-1)||1)}else{Y=am.width/((aq-z)||(V-1)||1)}if(isNaN(w)){w=0;af=am.height/((V-1)||1)}else{af=am.height/((ap-w)||(V-1)||1)}ak.eachRecord(function(j,x){p=j.get(ak.xField);if(typeof p=="string"||typeof p=="object"&&!Ext.isDate(p)||l&&S.get(l)&&S.get(l).type=="Category"){if(p in ag){p=ag[p]}else{p=ag[p]=x}}B=j.get(ak.yField);if(typeof B=="undefined"||(typeof B=="string"&&!B)){if(Ext.isDefined(Ext.global.console)){Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ",j,p,B)}return}if(typeof B=="object"&&!Ext.isDate(B)||ay&&S.get(ay)&&S.get(ay).type=="Category"){B=x}Q.push(x);av.push(p);aa.push(B)});at=av.length;if(at>am.width){a=ak.shrink(av,aa,am.width);av=a.x;aa=a.y}ak.items=[];k=0;at=av.length;for(N=0;N<at;N++){p=av[N];B=aa[N];if(B===false){if(T.length==1){T=[]}I=true;ak.items.push(false);continue}else{H=(am.x+(p-z)*Y).toFixed(2);G=((am.y+am.height)-(B-w)*af).toFixed(2);if(I){I=false;T.push("M")}T=T.concat([H,G])}if((typeof r=="undefined")&&(typeof G!="undefined")){r=G;s=H}if(!ak.line||au.resizing){ar=ar.concat([H,am.y+am.height/2])}if(au.animate&&au.resizing&&ak.line){ak.line.setAttributes({path:ar},true);if(ak.fillPath){ak.fillPath.setAttributes({path:ar,opacity:0.2},true)}if(ak.line.shadows){ac=ak.line.shadows;for(M=0,q=ac.length;M<q;M++){h=ac[M];h.setAttributes({path:ar},true)}}}if(K){E=aA.getAt(k++);if(!E){E=Ext.chart.Shape[U](u,Ext.apply({group:[R,aA],x:0,y:0,translate:{x:+(f||H),y:e||(am.y+am.height/2)},value:'"'+p+", "+B+'"',zIndex:4000},an));E._to={translate:{x:+H,y:+G}}}else{E.setAttributes({value:'"'+p+", "+B+'"',x:0,y:0,hidden:false},true);E._to={translate:{x:+H,y:+G}}}}ak.items.push({series:ak,value:[p,B],point:[H,G],sprite:E,storeItem:ao.getAt(Q[N])});f=H;e=G}if(T.length<=1){return}if(ak.smooth){b=Ext.draw.Draw.smooth(T,L(O)?O:ak.defaultSmoothness)}d=O?b:T;if(au.markerIndex&&ak.previousPath){ad=ak.previousPath;if(!O){Ext.Array.erase(ad,1,2)}}else{ad=T}if(!ak.line){ak.line=u.add(Ext.apply({type:"path",group:R,path:ar,stroke:al.stroke||al.fill},al||{}));if(D){ak.line.setAttributes(Ext.apply({},ak.shadowOptions),true)}ak.line.setAttributes({fill:"none",zIndex:3000});if(!al.stroke&&P){ak.line.setAttributes({stroke:t[aw%P]},true)}if(D){ac=ak.line.shadows=[];for(ab=0;ab<q;ab++){ah=X[ab];ah=Ext.apply({},ah,{path:ar});h=u.add(Ext.apply({},{type:"path",group:C[ab]},ah));ac.push(h)}}}if(ak.fill){c=d.concat([["L",H,am.y+am.height],["L",s,am.y+am.height],["L",s,r]]);if(!ak.fillPath){ak.fillPath=u.add({group:R,type:"path",opacity:al.opacity||0.3,fill:al.fill||t[aw%P],path:ar})}}W=K&&aA.getCount();if(au.animate){A=ak.fill;o=ak.line;ae=ak.renderer(o,false,{path:d},N,ao);Ext.apply(ae,al||{},{stroke:al.stroke||al.fill});delete ae.fill;o.show(true);if(au.markerIndex&&ak.previousPath){ak.animation=ax=ak.onAnimate(o,{to:ae,from:{path:ad}})}else{ak.animation=ax=ak.onAnimate(o,{to:ae})}if(D){ac=o.shadows;for(M=0;M<q;M++){ac[M].show(true);if(au.markerIndex&&ak.previousPath){ak.onAnimate(ac[M],{to:{path:d},from:{path:ad}})}else{ak.onAnimate(ac[M],{to:{path:d}})}}}if(A){ak.fillPath.show(true);ak.onAnimate(ak.fillPath,{to:Ext.apply({},{path:c,fill:al.fill||t[aw%P],"stroke-width":0},al||{})})}if(K){k=0;for(N=0;N<at;N++){if(ak.items[N]){n=aA.getAt(k++);if(n){ae=ak.renderer(n,ao.getAt(N),n._to,N,ao);ak.onAnimate(n,{to:Ext.apply(ae,an||{})});n.show(true)}}}for(;k<W;k++){n=aA.getAt(k);n.hide(true)}}}else{ae=ak.renderer(ak.line,false,{path:d,hidden:false},N,ao);Ext.apply(ae,al||{},{stroke:al.stroke||al.fill});delete ae.fill;ak.line.setAttributes(ae,true);if(D){ac=ak.line.shadows;for(M=0;M<q;M++){ac[M].setAttributes({path:d,hidden:false},true)}}if(ak.fill){ak.fillPath.setAttributes({path:c,hidden:false},true)}if(K){k=0;for(N=0;N<at;N++){if(ak.items[N]){n=aA.getAt(k++);if(n){ae=ak.renderer(n,ao.getAt(N),n._to,N,ao);n.setAttributes(Ext.apply(an||{},ae||{}),true);n.show(true)}}}for(;k<W;k++){n=aA.getAt(k);n.hide(true)}}}if(au.markerIndex){if(ak.smooth){Ext.Array.erase(T,1,2)}else{Ext.Array.splice(T,1,0,T[1],T[2])}ak.previousPath=T}ak.renderLabels();ak.renderCallouts();ak.fireEvent("draw",ak);
         }
     });
 
-    Ext.isIE = function() {
-        return /trident/.test(Ext.userAgent);
-    }();
+    Ext.override(Ext.chart.Legend, {
+        updatePosition: function() {
+            var me = this,
+                x, y,
+                legendWidth = me.width,
+                legendHeight = me.height,
+                padding = me.padding,
+                chart = me.chart,
+                chartBBox = chart.chartBBox,
+                insets = chart.insetPadding,
+                chartWidth = chartBBox.width - (insets * 2),
+                chartHeight = chartBBox.height - (insets * 2),
+                chartX = chartBBox.x + insets,
+                chartY = chartBBox.y + insets,
+                surface = chart.surface,
+                mfloor = Math.floor;
+
+            if (me.isDisplayed()) {
+                // Find the position based on the dimensions
+                switch(me.position) {
+                    case "left":
+                        x = insets;
+                        y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
+                        break;
+                    case "right":
+                        x = mfloor(surface.width - legendWidth) - insets;
+                        y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
+                        break;
+                    case "top":
+                        x = mfloor((chartX + chartBBox.width) / 2 - legendWidth / 2) - 7;
+                        y = insets;
+                        break;
+                    case "bottom":
+                        x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
+                        y = mfloor(surface.height - legendHeight) - insets;
+                        break;
+                    default:
+                        x = mfloor(me.origX) + insets;
+                        y = mfloor(me.origY) + insets;
+                }
+                me.x = x;
+                me.y = y;
+
+                // Update the position of each item
+                Ext.each(me.items, function(item) {
+                    item.updatePosition();
+                });
+                // Update the position of the outer box
+                me.boxSprite.setAttributes(me.getBBox(), true);
+            }
+        }
+    });
+
+    Ext.override(Ext.chart.LegendItem, {
+        createLegend: function(config) {
+            var me = this,
+                index = config.yFieldIndex,
+                series = me.series,
+                seriesType = series.type,
+                idx = me.yFieldIndex,
+                legend = me.legend,
+                surface = me.surface,
+                refX = legend.x + me.x,
+                refY = legend.y + me.y,
+                bbox, z = me.zIndex,
+                markerConfig, label, mask,
+                radius, toggle = false,
+                seriesStyle = Ext.apply(series.seriesStyle, series.style),
+                labelMarkerSize = legend.labelMarkerSize || 10;
+
+            function getSeriesProp(name) {
+                var val = series[name];
+                return (Ext.isArray(val) ? val[idx] : val);
+            }
+
+            label = me.add('label', surface.add({
+                type: 'text',
+                x: 30,
+                y: 0,
+                zIndex: z || 0,
+                font: legend.labelFont,
+                fill: legend.labelColor || '#000',
+                text: getSeriesProp('title') || getSeriesProp('yField')
+            }));
+
+            if (seriesType === 'line' || seriesType === 'scatter') {
+                if (seriesType === 'line') {
+                    me.add('line', surface.add({
+                        type: 'path',
+                        path: 'M0.5,0.5L16.5,0.5',
+                        zIndex: z,
+                        "stroke-width": series.lineWidth,
+                        "stroke-linejoin": "round",
+                        "stroke-dasharray": series.dash,
+                        stroke: seriesStyle.stroke || '#000',
+                        style: {
+                            cursor: 'pointer'
+                        }
+                    }));
+                }
+                if (series.showMarkers || seriesType === 'scatter') {
+                    markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
+                    me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
+                        fill: markerConfig.fill,
+                        x: 8.5,
+                        y: 0.5,
+                        zIndex: z,
+                        radius: markerConfig.radius || markerConfig.size,
+                        style: {
+                            cursor: 'pointer'
+                        }
+                    }));
+                }
+            }
+            else {
+                me.add('box', surface.add({
+                    type: 'rect',
+                    zIndex: z,
+                    x: 6,
+                    y: 0,
+                    width: labelMarkerSize,
+                    height: labelMarkerSize,
+                    fill: series.getLegendColor(index),
+                    style: {
+                        cursor: 'pointer'
+                    }
+                }));
+            }
+
+            me.setAttributes({
+                hidden: false
+            }, true);
+
+            bbox = me.getBBox();
+
+            mask = me.add('mask', surface.add({
+                type: 'rect',
+                x: bbox.x,
+                y: bbox.y,
+                width: bbox.width || 20,
+                height: bbox.height || 20,
+                zIndex: (z || 0) + 1000,
+                fill: '#f00',
+                opacity: 0,
+                style: {
+                    'cursor': 'pointer'
+                }
+            }));
+
+
+            me.on('mouseover', function() {
+                label.setStyle({
+                    'font-weight': 'bold'
+                });
+                mask.setStyle({
+                    'cursor': 'pointer'
+                });
+                series._index = index;
+                series.highlightItem();
+            }, me);
+
+            me.on('mouseout', function() {
+                label.setStyle({
+                    'font-weight': 'normal'
+                });
+                series._index = index;
+                series.unHighlightItem();
+            }, me);
+
+            if (!series.visibleInLegend(index)) {
+                toggle = true;
+                label.setAttributes({
+                   opacity: 0.5
+                }, true);
+            }
+
+            me.on('mousedown', function() {
+                if (!toggle) {
+                    series.hideAll();
+                    label.setAttributes({
+                        opacity: 0.5
+                    }, true);
+                } else {
+                    series.showAll();
+                    label.setAttributes({
+                        opacity: 1
+                    }, true);
+                }
+                toggle = !toggle;
+            }, me);
+            me.updatePosition({x:0, y:0});
+        }
+    });
+
+    Ext.override(Ext.chart.axis.Axis, {
+        drawHorizontalLabels: function() {
+            var me = this,
+                labelConf = me.label,
+                floor = Math.floor,
+                max = Math.max,
+                axes = me.chart.axes,
+                position = me.position,
+                inflections = me.inflections,
+                ln = inflections.length,
+                labels = me.labels,
+                labelGroup = me.labelGroup,
+                maxHeight = 0,
+                ratio,
+                gutterY = me.chart.maxGutter[1],
+                ubbox, bbox, point, prevX, prevLabel,
+                projectedWidth = 0,
+                textLabel, attr, textRight, text,
+                label, last, x, y, i, firstLabel;
+
+            last = ln - 1;
+            // get a reference to the first text label dimensions
+            point = inflections[0];
+            firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
+            ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
+
+            for (i = 0; i < ln; i++) {
+                point = inflections[i];
+                text = me.label.renderer(labels[i]) || '';
+                textLabel = me.getOrCreateLabel(i, text);
+                bbox = textLabel._bbox;
+                maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
+                x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
+                if (me.chart.maxGutter[0] == 0) {
+                    if (i == 0 && axes.findIndex('position', 'left') == -1) {
+                        x = point[0];
+                    }
+                    else if (i == last && axes.findIndex('position', 'right') == -1) {
+                        x = point[0] - bbox.width;
+                    }
+                }
+                if (position == 'top') {
+                    y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
+                }
+                else {
+                    y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
+                }
+
+                var moveLabels = labelConf.rotate && labelConf.rotate.degrees && !Ext.Array.contains([0,90,180,270,360], labelConf.rotate.degrees),
+                    adjust = Math.floor((textLabel.text.length - 12) * -1 * 0.75),
+                    newX = moveLabels ? point[0] - textLabel._bbox.width + adjust: x;
+
+                textLabel.setAttributes({
+                    hidden: false,
+                    x: newX,
+                    y: y
+                }, true);
+
+                // skip label if there isn't available minimum space
+                if (i != 0 && (me.intersect(textLabel, prevLabel)
+                    || me.intersect(textLabel, firstLabel))) {
+                    textLabel.hide(true);
+                    continue;
+                }
+
+                prevLabel = textLabel;
+            }
+
+            return maxHeight;
+        }
+    });
+
+    Ext.override(Ext.chart.axis.Radial, {
+        drawLabel: function() {
+            var chart = this.chart,
+                surface = chart.surface,
+                bbox = chart.chartBBox,
+                store = chart.store,
+                centerX = bbox.x + (bbox.width / 2),
+                centerY = bbox.y + (bbox.height / 2),
+                rho = Math.min(bbox.width, bbox.height) /2,
+                max = Math.max, round = Math.round,
+                labelArray = [], label,
+                fields = [], nfields,
+                categories = [], xField,
+                aggregate = !this.maximum,
+                maxValue = this.maximum || 0,
+                steps = this.steps, i = 0, j, dx, dy,
+                pi2 = Math.PI * 2,
+                cos = Math.cos, sin = Math.sin,
+                display = this.label.display,
+                draw = display !== 'none',
+                margin = 10,
+
+                labelColor = '#333',
+                labelFont = 'normal 9px sans-serif',
+                seriesStyle = chart.seriesStyle;
+
+            labelColor = seriesStyle ? seriesStyle.labelColor : labelColor;
+            labelFont = seriesStyle ? seriesStyle.labelFont : labelFont;
+
+            if (!draw) {
+                return;
+            }
+
+            //get all rendered fields
+            chart.series.each(function(series) {
+                fields.push(series.yField);
+                xField = series.xField;
+            });
+
+            //get maxValue to interpolate
+            store.each(function(record, i) {
+                if (aggregate) {
+                    for (i = 0, nfields = fields.length; i < nfields; i++) {
+                        maxValue = max(+record.get(fields[i]), maxValue);
+                    }
+                }
+                categories.push(record.get(xField));
+            });
+            if (!this.labelArray) {
+                if (display != 'categories') {
+                    //draw scale
+                    for (i = 1; i <= steps; i++) {
+                        label = surface.add({
+                            type: 'text',
+                            text: round(i / steps * maxValue),
+                            x: centerX,
+                            y: centerY - rho * i / steps,
+                            'text-anchor': 'middle',
+                            'stroke-width': 0.1,
+                            stroke: '#333',
+                            fill: labelColor,
+                            font: labelFont
+                        });
+                        label.setAttributes({
+                            hidden: false
+                        }, true);
+                        labelArray.push(label);
+                    }
+                }
+                if (display != 'scale') {
+                    //draw text
+                    for (j = 0, steps = categories.length; j < steps; j++) {
+                        dx = cos(j / steps * pi2) * (rho + margin);
+                        dy = sin(j / steps * pi2) * (rho + margin);
+                        label = surface.add({
+                            type: 'text',
+                            text: categories[j],
+                            x: centerX + dx,
+                            y: centerY + dy,
+                            'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start'),
+                            fill: labelColor,
+                            font: labelFont
+                        });
+                        label.setAttributes({
+                            hidden: false
+                        }, true);
+                        labelArray.push(label);
+                    }
+                }
+            }
+            else {
+                labelArray = this.labelArray;
+                if (display != 'categories') {
+                    //draw values
+                    for (i = 0; i < steps; i++) {
+                        labelArray[i].setAttributes({
+                            text: round((i + 1) / steps * maxValue),
+                            x: centerX,
+                            y: centerY - rho * (i + 1) / steps,
+                            'text-anchor': 'middle',
+                            'stroke-width': 0.1,
+                            stroke: '#333',
+                            fill: labelColor,
+                            font: labelFont
+                        }, true);
+                    }
+                }
+                if (display != 'scale') {
+                    //draw text
+                    for (j = 0, steps = categories.length; j < steps; j++) {
+                        dx = cos(j / steps * pi2) * (rho + margin);
+                        dy = sin(j / steps * pi2) * (rho + margin);
+                        if (labelArray[i + j]) {
+                            labelArray[i + j].setAttributes({
+                                type: 'text',
+                                text: categories[j],
+                                x: centerX + dx,
+                                y: centerY + dy,
+                                'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start'),
+                                fill: labelColor,
+                                font: labelFont
+                            }, true);
+                        }
+                    }
+                }
+            }
+            this.labelArray = labelArray;
+        }
+    });
 
 	// namespace
 	EV = {};
@@ -33,13 +526,15 @@
 
 		// conf
 		(function() {
+
+            // finals
 			conf.finals = {
 				dimension: {
 					data: {
 						value: 'data',
-						name: NS.i18n.data,
-						dimensionName: 'dx',
-						objectName: 'dx',
+						name: NS.i18n.data || 'Data',
+						dimensionName: 'dy',
+						objectName: 'dy',
 						warning: {
 							filter: '...'//NS.i18n.wm_multiple_filter_ind_de
 						}
@@ -150,6 +645,7 @@
 			dimConf.objectNameMap[dimConf.organisationUnit.objectName] = dimConf.organisationUnit;
 			dimConf.objectNameMap[dimConf.dimension.objectName] = dimConf.dimension;
 
+            // period
 			conf.period = {
 				periodTypes: [
 					{id: 'Daily', name: NS.i18n.daily},
@@ -165,6 +661,26 @@
 				]
 			};
 
+            // aggregation type
+            conf.aggregationType = {
+                data: [
+					{id: 'COUNT', name: NS.i18n.count, text: NS.i18n.count},
+					{id: 'AVERAGE', name: NS.i18n.average, text: NS.i18n.average},
+					{id: 'SUM', name: NS.i18n.sum, text: NS.i18n.sum},
+					{id: 'STDDEV', name: NS.i18n.stddev, text: NS.i18n.stddev},
+					{id: 'VARIANCE', name: NS.i18n.variance, text: NS.i18n.variance},
+					{id: 'MIN', name: NS.i18n.min, text: NS.i18n.min},
+					{id: 'MAX', name: NS.i18n.max, text: NS.i18n.max}
+                ],
+                idNameMap: {}
+            };
+
+            for (var i = 0, obj; i < conf.aggregationType.data.length; i++) {
+                obj = conf.aggregationType.data[i];
+                conf.aggregationType.idNameMap[obj.id] = obj.text;
+            }
+
+            // gui layout
 			conf.layout = {
 				west_width: 452,
 				west_fill: 2,
@@ -200,6 +716,7 @@
 				multiselect_fill_reportingrates: 315
 			};
 
+            // chart
 			conf.chart = {
                 style: {
                     inset: 30,
@@ -210,6 +727,15 @@
                 }
             };
 
+            // report
+			conf.report = {
+				digitGroupSeparator: {
+					'comma': ',',
+					'space': ' '
+				}
+			};
+
+            // url
             conf.url = {
                 analysisFields: [
                     '*',
@@ -789,6 +1315,29 @@
                 return o;
             };
 
+            support.prototype.array.getObjectDataById = function(array, sourceArray, properties, idProperty) {
+                array = Ext.Array.from(array);
+                sourceArray = Ext.Array.from(sourceArray);
+                properties = Ext.Array.from(properties);
+                idProperty = idProperty || 'id';
+
+                for (var i = 0, obj; i < array.length; i++) {
+                    obj = array[i];
+
+                    for (var j = 0, sourceObj; j < sourceArray.length; j++) {
+                        sourceObj = sourceArray[j];
+
+                        if (Ext.isString(obj[idProperty]) && sourceObj[idProperty] && obj[idProperty].indexOf(sourceObj.id) !== -1) {
+                            for (var k = 0, property; k < properties.length; k++) {
+                                property = properties[k];
+
+                                obj[property] = sourceObj[property];
+                            }
+                        }
+                    }
+                }
+            };
+
 				// object
 			support.prototype.object = {};
 
@@ -871,6 +1420,10 @@
 			support.prototype.number.prettyPrint = function(number, separator) {
 				separator = separator || 'space';
 
+                if (!Ext.isNumber(number)) {
+                    return;
+                }
+
 				if (separator === 'none') {
 					return number;
 				}
@@ -1301,6 +1854,32 @@
                     return xLayout;
                 };
 
+                // collapse data dimensions?
+                (function() {
+                    var keys = xLayout.collapseDataDimensions ? ['dy', 'pe', 'ou'] : ['dy'],
+                        dimensionsToRemove = [];
+
+                    // find dimensions to remove
+                    for (var i = 0, dim; i < dimensions.length; i++) {
+                        dim = dimensions[i];
+
+                        if (xLayout.collapseDataDimensions && !Ext.Array.contains(keys, dim.dimension)) {
+                            dimensionsToRemove.push(dim);
+                        }
+                        else if (!xLayout.collapseDataDimensions && Ext.Array.contains(keys, dim.dimension)) {
+                            dimensionsToRemove.push(dim);
+                        }
+                    }
+
+                    // remove dimensions
+                    for (var i = 0, dim; i < dimensionsToRemove.length; i++) {
+                        removeDimensionFromXLayout(dimensionsToRemove[i].dimension);
+                    }
+
+                    // update dimensions array
+                    dimensions = Ext.Array.clean([].concat(xLayout.columns || [], xLayout.rows || [], xLayout.filters || []));
+                }());
+
                 // items
                 for (var i = 0, dim, header; i < dimensions.length; i++) {
                     dim = dimensions[i];
@@ -1320,30 +1899,34 @@
                     }
                 }
 
-                // restore order for options
+                // restore item order
                 for (var i = 0, orgDim; i < originalDimensions.length; i++) {
                     orgDim = originalDimensions[i];
 
+                    // if sorting and row dim, dont restore order
+                    if (layout.sorting && Ext.Array.contains(xLayout.rowDimensionNames, orgDim.dimension)) {
+                        continue;
+                    }
+
+                    // user specified options/legends
                     if (Ext.isString(orgDim.filter)) {
                         var a = orgDim.filter.split(':');
 
                         if (a[0] === 'IN' && a.length > 1 && Ext.isString(a[1])) {
-                            var options = a[1].split(';'),
-                                items = [];
+                            var options = a[1].split(';');
 
-                            for (var j = 0, dim; j < dimensions.length; j++) {
+                            for (var j = 0, dim, items; j < dimensions.length; j++) {
                                 dim = dimensions[j];
 
                                 if (dim.dimension === orgDim.dimension && dim.items && dim.items.length) {
-                                    var items = [];
+                                    items = [];
 
                                     for (var k = 0, option; k < options.length; k++) {
                                         option = options[k];
 
                                         for (var l = 0, item; l < dim.items.length; l++) {
                                             item = dim.items[l];
-
-                                            if (item.name === option) {
+                                            if (item.id === option || item.id === (dim.dimension + option)) {
                                                 items.push(item);
                                             }
                                         }
@@ -1354,6 +1937,21 @@
                             }
                         }
                     }
+                    // no specified legends -> sort by start value
+                    else if (orgDim.legendSet && orgDim.legendSet.id) {
+                        for (var j = 0, dim, items; j < dimensions.length; j++) {
+                            dim = dimensions[j];
+
+                            if (dim.dimension === orgDim.dimension && dim.items && dim.items.length) {
+
+                                // get start/end value
+                                support.prototype.array.getObjectDataById(dim.items, init.idLegendSetMap[orgDim.legendSet.id].legends, ['startValue', 'endValue']);
+
+                                // sort by start value
+                                support.prototype.array.sort(dim.items, 'ASC', 'startValue');
+                            }
+                        }
+                    }
                 }
 
                 // re-layout
@@ -1997,7 +2595,10 @@
 			web.analytics.getParamString = function(layout, format) {
                 var paramString,
                     dimensions = Ext.Array.clean([].concat(layout.columns || [], layout.rows || [])),
-                    ignoreKeys = ['longitude', 'latitude'],
+                    ignoreKeys = ['dy', 'longitude', 'latitude'],
+                    dataTypeMap = {
+                        'aggregated_values': 'aggregate'
+                    },
                     nameItemsMap;
 
                 paramString = '/api/analytics/events/aggregate/' + layout.program.id + '.' + (format || 'json') + '?';
@@ -2025,6 +2626,13 @@
 								paramString += encodeURIComponent(item.id) + ((j < (dim.items.length - 1)) ? ';' : '');
 							}
 						}
+                        else if (Ext.isObject(dim.legendSet) && dim.legendSet.id) {
+                            paramString += '-' + dim.legendSet.id;
+
+                            if (dim.filter) {
+                                paramString += ':' + encodeURIComponent(dim.filter);
+                            }
+                        }
 						else {
 							paramString += dim.filter ? ':' + encodeURIComponent(dim.filter) : '';
 						}
@@ -2046,6 +2654,13 @@
                                 paramString += j < dim.items.length - 1 ? ';' : '';
                             }
                         }
+                        else if (Ext.isObject(dim.legendSet) && dim.legendSet.id) {
+                            paramString += '-' + dim.legendSet.id;
+
+                            if (dim.filter) {
+                                paramString += ':' + encodeURIComponent(dim.filter);
+                            }
+                        }
                         else {
                             paramString += dim.filter ? ':' + encodeURIComponent(dim.filter) : '';
                         }
@@ -2078,6 +2693,11 @@
                 // display property
                 paramString += '&displayProperty=' + init.userAccount.settings.keyAnalysisDisplayProperty.toUpperCase();
 
+                // collapse data items
+                if (layout.collapseDataDimensions) {
+                    paramString += '&collapseDataDimensions=true';
+                }
+
                 return paramString;
             };
 
@@ -2454,7 +3074,12 @@
                         fields: store.numericFields,
                         minimum: minimum < 0 ? minimum : 0,
                         label: {
-                            renderer: Ext.util.Format.numberRenderer(renderer)
+                            //renderer: Ext.util.Format.numberRenderer(renderer),
+                            renderer: function(v) {
+                                return support.prototype.number.prettyPrint(v);
+                            },
+                            style: {},
+                            rotate: {}
                         },
                         labelTitle: {
                             font: 'bold 13px ' + conf.chart.style.fontFamily
@@ -2462,13 +3087,13 @@
                         grid: {
                             odd: {
                                 opacity: 1,
-                                stroke: '#aaa',
-                                'stroke-width': 0.1
+                                stroke: '#000',
+                                'stroke-width': 0.03
                             },
                             even: {
                                 opacity: 1,
-                                stroke: '#aaa',
-                                'stroke-width': 0.1
+                                stroke: '#000',
+                                'stroke-width': 0.03
                             }
                         }
                     };
@@ -2571,13 +3196,34 @@
                     };
 
                     if (xLayout.showValues) {
+                        var labelFont = conf.chart.style.fontFamily,
+                            labelColor = 'black';
+
+                        if (Ext.isObject(xLayout.seriesStyle)) {
+                            var style = xLayout.seriesStyle;
+
+                            // label
+                            labelColor = style.labelColor || labelColor;
+
+                            if (style.labelFont) {
+                                labelFont = style.labelFont;
+                            }
+                            else {
+                                labelFont = style.labelFontWeight ? style.labelFontWeight + ' ' : 'normal ';
+                                labelFont += style.labelFontSize ? parseFloat(style.labelFontSize) + 'px ' : '11px ';
+                                labelFont +=  style.labelFontFamily ? style.labelFontFamily : conf.chart.style.fontFamily;
+                            }
+                        }
+
                         main.label = {
                             display: 'outside',
                             'text-anchor': 'middle',
                             field: store.rangeFields,
-                            font: conf.chart.style.fontFamily,
+                            font: labelFont,
+                            fill: labelColor,
                             renderer: function(n) {
-                                return n === '0.0' ? '' : n;
+                                n = n === '0.0' ? '' : n;
+                                return support.prototype.number.prettyPrint(n);
                             }
                         };
                     }
@@ -2655,7 +3301,7 @@
                         renderer: function(si, item) {
                             if (item.value) {
                                 var value = item.value[1] === '0.0' ? '-' : item.value[1];
-                                this.update('<div style="text-align:center"><div style="font-size:17px; font-weight:bold">' + value + '</div><div style="font-size:10px">' + si.data[conf.finals.data.domain] + '</div></div>');
+                                this.update('<div style="font-size:17px; font-weight:bold">' + support.prototype.number.prettyPrint(value) + '</div><div style="font-size:10px">' + si.data[conf.finals.data.domain] + '</div>');
                             }
                         }
                     };
@@ -2802,8 +3448,10 @@
                                                 tmpText = '';
 
                                             if (operator === 'IN') {
-                                                for (var ii = 0; ii < valueArray.length; ii++) {
-                                                    tmpText += (tmpText.length ? ', ' : '') + valueArray[ii];
+                                                for (var ii = 0, value; ii < valueArray.length; ii++) {
+                                                    value = valueArray[ii];
+
+                                                    tmpText += (tmpText.length ? ', ' : '') + (md.booleanNames[value] || md.optionNames[value] || md.names[value] || value);
                                                 }
 
                                                 text += tmpText;
@@ -2841,6 +3489,14 @@
                         }
                     }
 
+                    // aggregation type
+                    if (Ext.isObject(layout.value) && layout.value.id && layout.aggregationType) {
+                        var value = layout.value.id;
+
+                        text += text.length ? ', ' : '';
+                        text += (md.booleanNames[value] || md.optionNames[value] || md.names[value] || value) + ' (' + conf.aggregationType.idNameMap[layout.aggregationType] + ')';
+                    }
+
                     fontSize = (centerRegion.getWidth() / text.length) < 11.6 ? 13 : 18;
 
                     return Ext.create('Ext.draw.Sprite', {
@@ -3340,12 +3996,12 @@
 			}
 
 			// legend set map
-			//init.idLegendSetMap = {};
+			init.idLegendSetMap = {};
 
-			//for (var i = 0, set; i < init.legendSets.length; i++) {
-				//set = init.legendSets[i];
-				//init.idLegendSetMap[set.id] = set;
-			//}
+			for (var i = 0, set; i < init.legendSets.length; i++) {
+				set = init.legendSets[i];
+				init.idLegendSetMap[set.id] = set;
+			}
 		}());
 
 		// instance

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/styles/style.css'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/styles/style.css	2015-02-25 14:51:26 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-visualizer/styles/style.css	2015-04-08 13:50:25 +0000
@@ -312,6 +312,23 @@
 
 
 /*----------------------------------------------------------------------------
+ * NS viewport
+ *--------------------------------------------------------------------------*/
+
+.ns-viewport-text * {
+    padding: 3px 10px;
+    font-size: 11px;
+    color: #515a62;
+}
+.ns-viewport-text h3 {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    padding: 0 0 8px 0;
+}
+
+
+/*----------------------------------------------------------------------------
  * Panel
  *--------------------------------------------------------------------------*/
 
@@ -414,13 +431,13 @@
 
 	/* Custom */
 .ns-combo.h21 .x-form-trigger {
-    height: 21px;
+    height: 21px !important;
 }
 .ns-combo.h22 .x-form-trigger {
-    height: 22px;
+    height: 22px !important;
 }
 .ns-combo.h24 .x-form-trigger {
-    height: 24px;
+    height: 24px !important;
 }
 
 /*----------------------------------------------------------------------------
@@ -795,6 +812,19 @@
     cursor: default !important;
 }
 
+	/* Link button */
+.ns-linkbutton.x-btn-default-small {
+    border: 1px solid transparent;
+    background: transparent;
+}
+.ns-linkbutton .x-btn-inner {
+    color: #3162c5;
+    padding: 0;
+}
+.ns-linkbutton.x-btn-over .x-btn-inner {
+    text-decoration: underline;
+}
+
 
 /*----------------------------------------------------------------------------
  * Tooltip

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/scripts/app.js	2015-03-31 16:30:59 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/scripts/app.js	2015-04-08 13:50:25 +0000
@@ -6226,18 +6226,17 @@
 					ns.app.centerRegion = this;
 				},
 				afterrender: function(p) {
-					var liStyle = 'padding:3px 10px; color:#333',
-						html = '';
+					var html = '';
 
-					html += '<div style="padding:20px">';
-					html += '<div style="font-size:14px; padding-bottom:8px">' + NS.i18n.example1 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example2 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example3 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example4 + '</div>';
-					html += '<div style="font-size:14px; padding-top:20px; padding-bottom:8px">' + NS.i18n.example5 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example6 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example7 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example8 + '</div>';
+					html += '<div class="ns-viewport-text" style="padding:20px">';
+					html += '<h3>' + NS.i18n.example1 + '</h3>';
+					html += '<div>- ' + NS.i18n.example2 + '</div>';
+					html += '<div>- ' + NS.i18n.example3 + '</div>';
+					html += '<div>- ' + NS.i18n.example4 + '</div>';
+					html += '<h3 style="padding-top:20px">' + NS.i18n.example5 + '</h3>';
+					html += '<div>- ' + NS.i18n.example6 + '</div>';
+					html += '<div>- ' + NS.i18n.example7 + '</div>';
+					html += '<div>- ' + NS.i18n.example8 + '</div>';
 					html += '</div>';
 
 					p.update(html);

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/styles/style.css'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/styles/style.css	2015-03-31 14:29:54 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-pivot/styles/style.css	2015-04-08 13:50:25 +0000
@@ -312,6 +312,23 @@
 
 
 /*----------------------------------------------------------------------------
+ * NS viewport
+ *--------------------------------------------------------------------------*/
+
+.ns-viewport-text * {
+    padding: 3px 10px;
+    font-size: 11px;
+    color: #515a62;
+}
+.ns-viewport-text h3 {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    padding: 0 0 8px 0;
+}
+
+
+/*----------------------------------------------------------------------------
  * Panel
  *--------------------------------------------------------------------------*/
 

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/scripts/app.js	2015-03-31 16:28:36 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/scripts/app.js	2015-04-08 13:50:25 +0000
@@ -6314,18 +6314,17 @@
 					}
                 },
 				afterrender: function(p) {
-					var liStyle = 'padding:3px 10px; color:#333',
-						html = '';
+					var html = '';
 
-					html += '<div style="padding:20px">';
-					html += '<div style="font-size:14px; padding-bottom:8px">' + NS.i18n.example1 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example2 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example3 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example4 + '</div>';
-					html += '<div style="font-size:14px; padding-top:20px; padding-bottom:8px">' + NS.i18n.example5 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example6 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example7 + '</div>';
-					html += '<div style="' + liStyle + '">- ' + NS.i18n.example8 + '</div>';
+					html += '<div class="ns-viewport-text" style="padding:20px">';
+					html += '<h3>' + NS.i18n.example1 + '</h3>';
+					html += '<div>- ' + NS.i18n.example2 + '</div>';
+					html += '<div>- ' + NS.i18n.example3 + '</div>';
+					html += '<div>- ' + NS.i18n.example4 + '</div>';
+					html += '<h3 style="padding-top:20px">' + NS.i18n.example5 + '</h3>';
+					html += '<div>- ' + NS.i18n.example6 + '</div>';
+					html += '<div>- ' + NS.i18n.example7 + '</div>';
+					html += '<div>- ' + NS.i18n.example8 + '</div>';
 					html += '</div>';
 
 					p.update(html);

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/styles/style.css'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/styles/style.css	2015-03-09 13:15:43 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-visualizer/styles/style.css	2015-04-08 13:50:25 +0000
@@ -131,6 +131,23 @@
 
 
 /*----------------------------------------------------------------------------
+ * NS viewport
+ *--------------------------------------------------------------------------*/
+
+.ns-viewport-text * {
+    padding: 3px 10px;
+    font-size: 11px;
+    color: #515a62;
+}
+.ns-viewport-text h3 {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    padding: 0 0 8px 0;
+}
+
+
+/*----------------------------------------------------------------------------
  * Panel
  *--------------------------------------------------------------------------*/