]> arthur.barton.de Git - netdata.git/blobdiff - web/dashboard.js
fixed minor issues throughout the code (mainly types); dashboard has now a watermark...
[netdata.git] / web / dashboard.js
index 5b5e0b4b8c5eab1905076af00c8a9ef0b71da6ba..ea084348af246af09ff16152e99466b81b97773f 100755 (executable)
                        var a = i.split('.');
 
                        if(a[0] === 'options') {
-                               if(a[1] === 'options.setOptionCallback') continue;
+                               if(a[1] === 'setOptionCallback') continue;
                                if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
                                if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
 
                // hidden all the not-visible charts
                // using this little function we try to switch
                // the charts back to visible quickly
-               var targets = NETDATA.options.targets;
-               var len = targets.length;
-               while(len--) targets[len].isVisible();
+               //var targets = NETDATA.options.targets;
+               //var len = targets.length;
+               //while(len--) targets[len].isVisible();
        }
 
        // ----------------------------------------------------------------------------------------------------------------
        // dimensions selection
 
        // FIXME
+       // move color assignment to dimensions, here
 
        dimensionStatus = function(parent, label, name_div, value_div, color) {
                this.enabled = false;
 
 
        // ----------------------------------------------------------------------------------------------------------------
-       // Our state object, where all per-chart values are stored
+       // global selection sync
 
-       resizeCallback = function(state) {
-               this.state = state;
-               this.resize = function(height) {
-                       this.state
+       NETDATA.globalSelectionSync = {
+               state: null,
+               dont_sync_before: 0,
+               last_t: 0,
+               slaves: [],
+
+               stop: function() {
+                       if(this.state !== null)
+                               this.state.globalSelectionSyncStop();
+               },
+
+               delay: function() {
+                       if(this.state !== null) {
+                               this.state.globalSelectionSyncDelay();
+                       }
                }
-       }
+       };
+
+       // ----------------------------------------------------------------------------------------------------------------
+       // Our state object, where all per-chart values are stored
 
        chartState = function(element) {
                var self = $(element);
+               this.element = element;
+
+               // IMPORTANT:
+               // all private functions should use 'that', instead of 'this'
+               var that = this;
+
+               /* error() - private
+                * show an error instead of the chart
+                */
+               var error = function(msg) {
+                       that.element.innerHTML = that.id + ': ' + msg;
+                       that.enabled = false;
+                       that.current = that.pan;
+               }
 
                // GUID - a unique identifier for the chart
                this.uuid = NETDATA.guid();
                this.height = self.data('height') || NETDATA.chartDefaults.height;
 
                if(this.settings_id !== null) {
-                       var me = this;
                        this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
-                               me.resizeAndRedrawChart(height);
+                               // this is the callback that will be called
+                               // if and when the user resets all localStorage variables
+                               // to their defaults
+
+                               resizeChartToHeight(height);
                        });
                }
 
-               $.extend(this, {
-                       // string - the netdata server URL, without any path
-                       host: self.data('host') || NETDATA.chartDefaults.host,
-
-                       // string - the grouping method requested by the user
-                       method: self.data('method') || NETDATA.chartDefaults.method,
-
-                       // the time-range requested by the user
-                       after: self.data('after') || NETDATA.chartDefaults.after,
-                       before: self.data('before') || NETDATA.chartDefaults.before,
-
-                       // the pixels per point requested by the user
-                       pixels_per_point: self.data('pixels-per-point') || 1,
-                       points: self.data('points') || null,
-
-                       // the dimensions requested by the user
-                       dimensions: self.data('dimensions') || null,
-
-                       // the chart library requested by the user
-                       library_name: self.data('chart-library') || NETDATA.chartDefaults.library,
-                       library: null,                  // object - the chart library used
-
-                       colors: null,
-                       colors_assigned: {},
-                       colors_available: null,
-
-                       element: element,               // the element already created by the user
-                       element_message: null,
-                       element_loading: null,
-                       element_chart: null,    // the element with the chart
-                       element_chart_id: null,
-                       element_legend: null,   // the element with the legend of the chart (if created by us)
-                       element_legend_id: null,
-                       element_legend_childs: {
-                               hidden: null,
-                               title_date: null,
-                               title_time: null,
-                               title_units: null,
-                               nano: null,
-                               nano_options: null,
-                               series: null
-                       },
-
-                       chart_url: null,                // string - the url to download chart info
-                       chart: null,                    // object - the chart as downloaded from the server
-
-                       validated: false,               // boolean - has the chart been validated?
-                       enabled: true,                  // boolean - is the chart enabled for refresh?
-                       paused: false,                  // boolean - is the chart paused for any reason?
-                       selected: false,                // boolean - is the chart shown a selection?
-                       debug: false,                   // boolean - console.log() debug info about this chart
+               // string - the netdata server URL, without any path
+               this.host = self.data('host') || NETDATA.chartDefaults.host;
 
-                       dom_created: false,             // boolean - is the DOM for the chart created?
-                       chart_created: false,   // boolean - is the library.create() been called?
-
-                       updates_counter: 0,             // numeric - the number of refreshes made so far
-                       updates_since_last_creation: 0,
+               // make sure the host does not end with /
+               // all netdata API requests use absolute paths
+               while(this.host.slice(-1) === '/')
+                       this.host = this.host.substring(0, this.host.length - 1);
 
-                       tm: {
-                               last_info_downloaded: 0,        // milliseconds - the timestamp we downloaded the chart
+               // string - the grouping method requested by the user
+               this.method = self.data('method') || NETDATA.chartDefaults.method;
 
-                               last_updated: 0,                        // the timestamp the chart last updated with data
+               // the time-range requested by the user
+               this.after = self.data('after') || NETDATA.chartDefaults.after;
+               this.before = self.data('before') || NETDATA.chartDefaults.before;
 
-                               pan_and_zoom_seq: 0,            // the sequence number of the global synchronization
-                                                                                       // between chart.
-                                                                                       // Used with NETDATA.globalPanAndZoom.seq
+               // the pixels per point requested by the user
+               this.pixels_per_point = self.data('pixels-per-point') || 1;
+               this.points = self.data('points') || null;
 
-                               last_visible_check: 0,          // the time we last checked if it is visible
+               // the dimensions requested by the user
+               this.dimensions = self.data('dimensions') || null;
 
-                               last_resized: 0,                        // the time the chart was resized
-                               last_hidden: 0,                         // the time the chart was hidden
-                               last_unhidden: 0,                       // the time the chart was unhidden
+               // the chart library requested by the user
+               this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
 
-                               last_autorefreshed: 0           // the time the chart was last refreshed
-                       },
+               // object - the chart library used
+               this.library = null;
 
-                       data: null,                             // the last data as downloaded from the netdata server
-                       data_url: 'invalid://', // string - the last url used to update the chart
-                       data_points: 0,                 // number - the number of points returned from netdata
-                       data_after: 0,                  // milliseconds - the first timestamp of the data
-                       data_before: 0,                 // milliseconds - the last timestamp of the data
-                       data_update_every: 0,   // milliseconds - the frequency to update the data
-                       netdata_first: 0,               // milliseconds - the first timestamp in netdata
-                       netdata_last: 0,                // milliseconds - the last timestamp in netdata
-
-                       dimensions_visibility: new dimensionsVisibility(this),
-
-                       current: null,                  // auto, pan, zoom
-                                                                       // this is a pointer to one of the sub-classes below
-
-                       auto: {
-                               name: 'auto',
-                               autorefresh: true,
-                               force_update_at: 0, // the timestamp to force the update at
-                               force_before_ms: null,
-                               force_after_ms: null,
-                               requested_before_ms: null,
-                               requested_after_ms: null,
-                       },
-                       pan: {
-                               name: 'pan',
-                               autorefresh: false,
-                               force_update_at: 0, // the timestamp to force the update at
-                               force_before_ms: null,
-                               force_after_ms: null,
-                               requested_before_ms: null,
-                               requested_after_ms: null,
-                       },
-                       zoom: {
-                               name: 'zoom',
-                               autorefresh: false,
-                               force_update_at: 0, // the timestamp to force the update at
-                               force_before_ms: null,
-                               force_after_ms: null,
-                               requested_before_ms: null,
-                               requested_after_ms: null,
-                       },
+               // color management
+               this.colors = null;
+               this.colors_assigned = {};
+               this.colors_available = null;
 
-                       refresh_dt_ms: 0,               // milliseconds - the time the last refresh took
-                       refresh_dt_element_name: self.data('dt-element-name') || null,  // string - the element to print refresh_dt_ms
-                       refresh_dt_element: null
-               });
+               // the element already created by the user
+               this.element_message = null;
 
-               this.init();
-       }
+               // the element with the chart
+               this.element_chart = null;
 
-       // ----------------------------------------------------------------------------------------------------------------
-       // Chart Resize
-
-       // this is actual chart resize algorithm
-       // it will:
-       // - resize the entire container
-       // - update the internal states
-       // - resize the chart as the div changes height
-       // - update the scrollbar of the legend
-       chartState.prototype.resizeAndRedrawChart = function(h) {
-               // console.log(h);
-               this.element.style.height = h;
-
-               if(this.settings_id !== null)
-                       NETDATA.localStorageSet('chart_heights.' + this.settings_id, h);
-
-               var now = new Date().getTime();
-               NETDATA.options.last_page_scroll = now;
-               NETDATA.options.last_resized = now;
-               NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
-
-               if(typeof this.library.resize === 'function' && this.element_chart !== null)
-                       this.library.resize(this);
-
-               if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
-                       $(this.element_legend_childs.nano).nanoScroller();
-       };
+               // the element with the legend of the chart (if created by us)
+               this.element_legend = null;
+               this.element_legend_childs = {
+                       hidden: null,
+                       title_date: null,
+                       title_time: null,
+                       title_units: null,
+                       nano: null,
+                       nano_options: null,
+                       series: null
+               };
 
-       chartState.prototype.resizeHandler = function(e) {
-               e.preventDefault();
+               this.chart_url = null;          // string - the url to download chart info
+               this.chart = null;                      // object - the chart as downloaded from the server
+
+               this.validated = false;                 // boolean - has the chart been validated?
+               this.enabled = true;                    // boolean - is the chart enabled for refresh?
+               this.paused = false;                    // boolean - is the chart paused for any reason?
+               this.selected = false;          // boolean - is the chart shown a selection?
+               this.debug = false;                     // boolean - console.log() debug info about this chart
+
+               this.netdata_first = 0;                 // milliseconds - the first timestamp in netdata
+               this.netdata_last = 0;                  // milliseconds - the last timestamp in netdata
+               this.requested_after = null;    // milliseconds - the timestamp of the request after param
+               this.requested_before = null;   // milliseconds - the timestamp of the request before param
+
+               this.auto = {
+                       name: 'auto',
+                       autorefresh: true,
+                       force_update_at: 0, // the timestamp to force the update at
+                       force_before_ms: null,
+                       force_after_ms: null
+               };
+               this.pan = {
+                       name: 'pan',
+                       autorefresh: false,
+                       force_update_at: 0, // the timestamp to force the update at
+                       force_before_ms: null,
+                       force_after_ms: null
+               };
+               this.zoom = {
+                       name: 'zoom',
+                       autorefresh: false,
+                       force_update_at: 0, // the timestamp to force the update at
+                       force_before_ms: null,
+                       force_after_ms: null
+               };
 
-               if(typeof this.event_resize === 'undefined'
-                       || this.event_resize.chart_original_w === 'undefined'
-                       || this.event_resize.chart_original_h === 'undefined')
-                       this.event_resize = {
-                               chart_original_w: this.element.clientWidth,
-                               chart_original_h: this.element.clientHeight,
-                               last: 0
-                       };
+               // this is a pointer to one of the sub-classes below
+               // auto, pan, zoom
+               this.current = this.auto;
 
-               if(e.type === 'touchstart') {
-                       this.event_resize.mouse_start_x = e.touches.item(0).pageX;
-                       this.event_resize.mouse_start_y = e.touches.item(0).pageY;
+               // check the requested library is available
+               // we don't initialize it here - it will be initialized when
+               // this chart will be first used
+               if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
+                       NETDATA.error(402, that.library_name);
+                       error('chart library "' + that.library_name + '" is not found');
+                       return;
                }
-               else {
-                       this.event_resize.mouse_start_x = e.clientX;
-                       this.event_resize.mouse_start_y = e.clientY;
+               else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
+                       NETDATA.error(403, that.library_name);
+                       error('chart library "' + that.library_name + '" is not enabled');
+                       return;
                }
+               else
+                       that.library = NETDATA.chartLibraries[that.library_name];
 
-               this.event_resize.chart_start_w = this.element.clientWidth;
-               this.event_resize.chart_start_h = this.element.clientHeight;
-               this.event_resize.chart_last_w = this.element.clientWidth;
-               this.event_resize.chart_last_h = this.element.clientHeight;
+               // milliseconds - the time the last refresh took
+               this.refresh_dt_ms = 0;
 
-               var now = new Date().getTime();
-               if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
-                       // double click / double tap event
+               // if we need to report the rendering speed
+               // find the element that needs to be updated
+               var refresh_dt_element_name = self.data('dt-element-name') || null;     // string - the element to print refresh_dt_ms
 
-                       // the optimal height of the chart
-                       // showing the entire legend
-                       var optimal = this.event_resize.chart_last_h
-                                       + this.element_legend_childs.content.scrollHeight
-                                       - this.element_legend_childs.content.clientHeight;
+               if(refresh_dt_element_name !== null)
+                       this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
+               else
+                       this.refresh_dt_element = null;
 
-                       // if we are not optimal, be optimal
-                       if(this.event_resize.chart_last_h != optimal)
-                               this.resizeAndRedrawChart(optimal.toString() + 'px');
+               this.dimensions_visibility = new dimensionsVisibility(this);
 
-                       // else if we do not have the original height
-                       // reset to the original height
-                       else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
-                               this.resizeAndRedrawChart(this.event_resize.chart_original_h.toString() + 'px');
-               }
-               else {
-                       this.event_resize.last = now;
-                       var self = this;
+               // ============================================================================================================
+               // PRIVATE FUNCTIONS
 
-                       // process movement event
-                       document.onmousemove =
-                       document.ontouchmove =
-                       this.element_legend_childs.resize_handler.onmousemove =
-                       this.element_legend_childs.resize_handler.ontouchmove =
-                               function(e) {
-                                       var y = null;
-
-                                       switch(e.type) {
-                                               case 'mousemove': y = e.clientY; break;
-                                               case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
-                                       }
+               var createDOM = function() {
+                       if(that.enabled == false) return;
 
-                                       if(y !== null) {
-                                               var     newH = self.event_resize.chart_start_h + y - self.event_resize.mouse_start_y;
+                       if(that.element_message !== null) that.element_message.innerHTML = '';
+                       if(that.element_legend !== null) that.element_legend.innerHTML = '';
+                       if(that.element_chart !== null) that.element_chart.innerHTML = '';
 
-                                               if(newH >= 70 && newH !== self.event_resize.chart_last_h) {
-                                                       self.resizeAndRedrawChart(newH.toString() + 'px');
-                                                       self.event_resize.chart_last_h = newH;
-                                               }
-                                       }
-                               };
+                       that.element.innerHTML = '';
 
-                       // process end event
-                       document.onmouseup = 
-                       document.ontouchend = 
-                       this.element_legend_childs.resize_handler.onmouseup =
-                       this.element_legend_childs.resize_handler.ontouchend =
-                               function(e) {
-                                       // remove all the hooks
-                                       document.onmouseup =
-                                       document.onmousemove =
-                                       document.ontouchmove =
-                                       document.ontouchend =
-                                       self.element_legend_childs.resize_handler.onmousemove =
-                                       self.element_legend_childs.resize_handler.ontouchmove =
-                                       self.element_legend_childs.resize_handler.onmouseout =
-                                       self.element_legend_childs.resize_handler.onmouseup =
-                                       self.element_legend_childs.resize_handler.ontouchend =
-                                               null;
-
-                                       // allow auto-refreshes
-                                       NETDATA.options.auto_refresher_stop_until = 0;
-                               };
-               }
-       }
+                       that.element_message = document.createElement('div');
+                       that.element_message.className = ' netdata-message hidden';
+                       that.element.appendChild(that.element_message);
 
-       
-       // ----------------------------------------------------------------------------------------------------------------
-       // global selection sync
+                       that.element_chart = document.createElement('div');
+                       that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
+                       that.element.appendChild(that.element_chart);
 
-       NETDATA.globalSelectionSync = {
-               state: null,
-               dont_sync_before: 0,
-               slaves: []
-       };
+                       if(that.hasLegend() === true) {
+                               that.element.className = "netdata-container-with-legend";
+                               that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
 
-       // prevent to global selection sync for some time
-       chartState.prototype.globalSelectionSyncDelay = function(ms) {
-               if(NETDATA.options.current.sync_selection === false)
-                       return;
+                               that.element_legend = document.createElement('div');
+                               that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
+                               that.element.appendChild(that.element_legend);
+                       }
+                       else {
+                               that.element.className = "netdata-container";
+                               that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
 
-               if(typeof ms === 'number')
-                       NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
-               else
-                       NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
-       }
+                               that.element_legend = null;
+                       }
+                       that.element_legend_childs.series = null;
 
-       // can we globally apply selection sync?
-       chartState.prototype.globalSelectionSyncAbility = function() {
-               if(NETDATA.options.current.sync_selection === false)
-                       return false;
+                       if(that.width !== 0)
+                               $(that.element).css('width', that.width);
 
-               if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime()) return false;
-               return true;
-       }
+                       if(that.height !== 0)
+                               $(that.element).css('height', that.height);
 
-       chartState.prototype.globalSelectionSyncIsMaster = function() {
-               if(NETDATA.globalSelectionSync.state === this)
-                       return true;
-               else
-                       return false;
-       }
+                       if(NETDATA.chartDefaults.min_width !== null)
+                               $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
 
-       // this chart is the master of the global selection sync
-       chartState.prototype.globalSelectionSyncBeMaster = function() {
-               // am I the master?
-               if(this.globalSelectionSyncIsMaster()) {
-                       if(this.debug === true)
-                               this.log('sync: I am the master already.');
+                       that.tm.last_dom_created = new Date().getTime();
 
-                       return;
+                       showLoading();
                }
 
-               if(NETDATA.globalSelectionSync.state) {
-                       if(this.debug === true)
-                               this.log('sync: I am not the sync master. Resetting global sync.');
+               /* init() private
+                * initialize state viariables
+                * destroy all (possibly) created state elements
+                * create the basic DOM for a chart
+                */
+               var init = function() {
+                       if(that.enabled == false) return;
 
-                       this.globalSelectionSyncStop();
-               }
+                       that.paused = false;
+                       that.selected = false;
 
-               // become the master
-               if(this.debug === true)
-                       this.log('sync: becoming sync master.');
+                       that.chart_created = false;             // boolean - is the library.create() been called?
+                       that.updates_counter = 0;               // numeric - the number of refreshes made so far
+                       that.updates_since_last_creation = 0;
 
-               this.selected = true;
-               NETDATA.globalSelectionSync.state = this;
+                       that.tm = {
+                               last_initialized: 0,            // milliseconds - the timestamp it was last initialized
+                               last_dom_created: 0,            // milliseconds - the timestamp its DOM was last created
+                               last_mode_switch: 0,            // milliseconds - the timestamp it switched modes
 
-               // find the all slaves
-               var targets = NETDATA.options.targets;
-               var len = targets.length;
-               while(len--) {
-                       st = targets[len];
+                               last_info_downloaded: 0,        // milliseconds - the timestamp we downloaded the chart
+                               last_updated: 0,                        // the timestamp the chart last updated with data
+                               pan_and_zoom_seq: 0,            // the sequence number of the global synchronization
+                                                                                       // between chart.
+                                                                                       // Used with NETDATA.globalPanAndZoom.seq
+                               last_visible_check: 0,          // the time we last checked if it is visible
+                               last_resized: 0,                        // the time the chart was resized
+                               last_hidden: 0,                         // the time the chart was hidden
+                               last_unhidden: 0,                       // the time the chart was unhidden
+                               last_autorefreshed: 0           // the time the chart was last refreshed
+                       },
 
-                       if(st === this) {
-                               if(this.debug === true)
-                                       st.log('sync: not adding me to sync');
-                       }
-                       else if(st.globalSelectionSyncIsEligible()) {
-                               if(this.debug === true)
-                                       st.log('sync: adding to sync as slave');
+                       that.data = null;                               // the last data as downloaded from the netdata server
+                       that.data_url = 'invalid://';   // string - the last url used to update the chart
+                       that.data_points = 0;                   // number - the number of points returned from netdata
+                       that.data_after = 0;                    // milliseconds - the first timestamp of the data
+                       that.data_before = 0;                   // milliseconds - the last timestamp of the data
+                       that.data_update_every = 0;             // milliseconds - the frequency to update the data
 
-                               st.globalSelectionSyncBeSlave();
-                       }
+                       that.tm.last_initialized = new Date().getTime();
+                       createDOM();
+
+                       that.setMode('auto');
                }
 
-               // this.globalSelectionSyncDelay(100);
-       }
+               var maxMessageFontSize = function() {
+                       // normally we want a font size, as tall as the element
+                       var h = that.element_message.clientHeight;
 
-       // can the chart participate to the global selection sync as a slave?
-       chartState.prototype.globalSelectionSyncIsEligible = function() {
-               if(this.enabled === true
-                       && this.library !== null
-                       && typeof this.library.setSelection === 'function'
-                       && this.isVisible()
-                       && this.dom_created === true
-                       && this.chart_created === true)
-                       return true;
+                       // but give it some air, 20% let's say, or 5 pixels min
+                       var lost = Math.max(h * 0.2, 5);
+                       h -= lost;
 
-               return false;
-       }
+                       // center the text, verically
+                       var paddingTop = (lost - 5) / 2;
 
-       // this chart is a slave of the global selection sync
-       chartState.prototype.globalSelectionSyncBeSlave = function() {
-               if(NETDATA.globalSelectionSync.state !== this)
-                       NETDATA.globalSelectionSync.slaves.push(this);
-       }
+                       // but check the width too
+                       // it should fit 10 characters in it
+                       var w = that.element_message.clientWidth / 10;
+                       if(h > w) {
+                               paddingTop += (h - w) / 2;
+                               h = w;
+                       }
 
-       // sync all the visible charts to the given time
-       // this is to be called from the chart libraries
-       chartState.prototype.globalSelectionSync = function(t) {
-               if(this.globalSelectionSyncAbility() === false) {
-                       if(this.debug === true)
-                               this.log('sync: cannot sync (yet?).');
+                       // and don't make it too huge
+                       // 5% of the screen size is good
+                       if(h > screen.height / 20) {
+                               paddingTop += (h - (screen.height / 20)) / 2;
+                               h = screen.height / 20;
+                       }
 
-                       return;
+                       // set it
+                       that.element_message.style.fontSize = h.toString() + 'px';
+                       that.element_message.style.paddingTop = paddingTop.toString() + 'px';
                }
 
-               if(this.globalSelectionSyncIsMaster() === false) {
-                       if(this.debug === true)
-                               this.log('sync: trying to be sync master.');
-
-                       this.globalSelectionSyncBeMaster();
+               var showMessage = function(msg) {
+                       that.element_message.className = 'netdata-message';
+                       that.element_message.innerHTML = msg;
+                       this.element_message.style.fontSize = 'x-small';
+                       that.element_message.style.paddingTop = '0px';
+                       that.___messageHidden___ = undefined;
+               }
 
-                       if(this.globalSelectionSyncAbility() === false) {
-                               if(this.debug === true)
-                                       this.log('sync: cannot sync (yet?).');
+               var showMessageIcon = function(icon) {
+                       that.element_message.innerHTML = icon;
+                       that.element_message.className = 'netdata-message icon';
+                       maxMessageFontSize();
+                       that.___messageHidden___ = undefined;
+               }
 
-                               return;
+               var hideMessage = function() {
+                       if(typeof that.___messageHidden___ === 'undefined') {
+                               that.___messageHidden___ = true;
+                               that.element_message.className = 'netdata-message hidden';
                        }
                }
 
-               $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
-                       st.setSelection(t);
-               });
-       }
-
-       // stop syncing all charts to the given time
-       chartState.prototype.globalSelectionSyncStop = function() {
-               if(NETDATA.globalSelectionSync.slaves.length) {
-                       if(this.debug === true)
-                               this.log('sync: cleaning up...');
-
-                       var self = this;
-                       $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
-                               if(st === self) {
-                                       if(self.debug === true)
-                                               st.log('sync: not adding me to sync stop');
-                               }
-                               else {
-                                       if(self.debug === true)
-                                               st.log('sync: removed slave from sync');
-
-                                       st.clearSelection();
-                               }
-                       });
+               var showRendering = function() {
+                       var icon;
+                       if(that.chart !== null) {
+                               if(that.chart.chart_type === 'line')
+                                       icon = '<i class="fa fa-line-chart"></i>';
+                               else
+                                       icon = '<i class="fa fa-area-chart"></i>';
+                       }
+                       else
+                               icon = '<i class="fa fa-area-chart"></i>';
 
-                       NETDATA.globalSelectionSync.slaves = [];
-                       NETDATA.globalSelectionSync.state = null;
+                       showMessageIcon(icon + ' netdata');
                }
 
-               // since we are the sync master, we should not call this.clearSelection()
-               // dygraphs is taking care of visualizing our selection.
-               this.selected = false;
-       }
-
-       chartState.prototype.setSelection = function(t) {
-               if(typeof this.library.setSelection === 'function') {
-                       if(this.library.setSelection(this, t) === true)
-                               this.selected = true;
-                       else
-                               this.selected = false;
+               var showLoading = function() {
+                       if(that.chart_created === false) {
+                               showMessageIcon('<i class="fa fa-refresh"></i> netdata');
+                               return true;
+                       }
+                       return false;
                }
-               else this.selected = true;
 
-               if(this.selected === true && this.debug === true)
-                       this.log('selection set to ' + t.toString());
+               // hide the chart, when it is not visible - called from isVisible()
+               var hideChart = function() {
+                       // no chart yet
+                       if(that.chart_created === false) return;
 
-               return this.selected;
-       }
+                       // we should destroy it
+                       if(NETDATA.options.current.destroy_on_hide === true) {
+                               init();
+                               that.___chartIsHidden___ = undefined;
+                               return;
+                       }
 
-       chartState.prototype.clearSelection = function() {
-               if(this.selected === true) {
-                       if(typeof this.library.clearSelection === 'function') {
-                               if(this.library.clearSelection(this) === true)
-                                       this.selected = false;
-                               else
-                                       this.selected = true;
+                       // just hide it, if it is not already hidden
+                       if(typeof that.___chartIsHidden___ === 'undefined') {
+                               showRendering();
+                               that.element_chart.style.display = 'none';
+                               if(that.element_legend !== null) that.element_legend.style.display = 'none';
+                               that.___chartIsHidden___ = true;
+                       }
+               }
+               
+               // unhide the chart, when it is visible - called from isVisible()
+               var unhideChart = function() {
+                       if(typeof that.___chartIsHidden___ !== 'undefined') {
+                               that.element_chart.style.display = 'inline-block';
+                               if(that.element_legend !== null) that.element_legend.style.display = 'inline-block';
+                               hideMessage();
+                               that.___chartIsHidden___ = undefined;
+                               resizeChart();
                        }
-                       else this.selected = false;
-                       
-                       if(this.selected === false && this.debug === true)
-                               this.log('selection cleared');
                }
 
-               this.legendReset();
-               return this.selected;
-       }
-
-       // find if a timestamp (ms) is shown in the current chart
-       chartState.prototype.timeIsVisible = function(t) {
-               if(t >= this.data_after && t <= this.data_before)
-                       return true;
-               return false;
-       },
+               // ----------------------------------------------------------------------------------------------------------------
+               // Chart Resize
 
-       chartState.prototype.calculateRowForTime = function(t) {
-               if(this.timeIsVisible(t) === false) return -1;
-               return Math.floor((t - this.data_after) / this.data_update_every);
-       }
+               // resizeChart() - private
+               // to be called just before the chart library to make sure that
+               // a properly sized dom is available
+               var resizeChart = function() {
+                       if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
+                               that.tm.last_resized = new Date().getTime();
+                               if(that.chart_created === false) return;
 
-       // ----------------------------------------------------------------------------------------------------------------
+                               if(that.needsRecreation())
+                                       init();
 
-       // console logging
-       chartState.prototype.log = function(msg) {
-               console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
-       }
+                               else if(typeof that.library.resize === 'function') {
+                                       that.library.resize(that);
 
-       chartState.prototype.pauseChart = function() {
-               if(this.paused === false) {
-                       if(this.debug === true)
-                               this.log('paused');
+                                       if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
+                                               $(that.element_legend_childs.nano).nanoScroller();
 
-                       this.paused = true;
+                                       maxMessageFontSize();
+                               }
+                       }
                }
-       }
-
-       chartState.prototype.unpauseChart = function() {
-               if(this.paused) {
-                       if(this.debug === true)
-                               this.log('unpaused');
 
-                       this.paused = false;
-               }
-       }
+               // this is the actual chart resize algorithm
+               // it will:
+               // - resize the entire container
+               // - update the internal states
+               // - resize the chart as the div changes height
+               // - update the scrollbar of the legend
+               var resizeChartToHeight = function(h) {
+                       // console.log(h);
+                       that.element.style.height = h;
 
-       chartState.prototype.resetChart = function() {
-               if(NETDATA.globalPanAndZoom.isMaster(this) && this.isVisible())
-                       NETDATA.globalPanAndZoom.clearMaster();
+                       if(that.settings_id !== null)
+                               NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
 
-               this.tm.pan_and_zoom_seq = 0;
+                       var now = new Date().getTime();
+                       NETDATA.options.last_page_scroll = now;
+                       NETDATA.options.last_resized = now;
+                       NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
 
-               this.clearSelection();
+                       // force a resize
+                       that.tm.last_resized = 0;
+                       resizeChart();
+               };
 
-               this.setMode('auto');
-               this.current.force_update_at = 0;
-               this.current.force_before_ms = null;
-               this.current.force_after_ms = null;
-               this.tm.last_autorefreshed = 0;
-               this.paused = false;
-               this.selected = false;
-               this.enabled = true;
-               this.debug = false;
-
-               // do not update the chart here
-               // or the chart will flip-flop when it is the master
-               // of a selection sync and another chart becomes
-               // the new master
-               if(NETDATA.options.current.sync_pan_and_zoom === false && this.isVisible() === true)
-                       this.updateChart();
-       }
+               this.resizeHandler = function(e) {
+                       e.preventDefault();
 
-       chartState.prototype.setMode = function(m) {
-               if(this.current) {
-                       if(this.current.name === m) return;
+                       if(typeof this.event_resize === 'undefined'
+                               || this.event_resize.chart_original_w === 'undefined'
+                               || this.event_resize.chart_original_h === 'undefined')
+                               this.event_resize = {
+                                       chart_original_w: this.element.clientWidth,
+                                       chart_original_h: this.element.clientHeight,
+                                       last: 0
+                               };
 
-                       this[m].requested_before_ms = this.current.requested_before_ms;
-                       this[m].requested_after_ms = this.current.requested_after_ms;
-               }
+                       if(e.type === 'touchstart') {
+                               this.event_resize.mouse_start_x = e.touches.item(0).pageX;
+                               this.event_resize.mouse_start_y = e.touches.item(0).pageY;
+                       }
+                       else {
+                               this.event_resize.mouse_start_x = e.clientX;
+                               this.event_resize.mouse_start_y = e.clientY;
+                       }
 
-               if(m === 'auto')
-                       this.current = this.auto;
-               else if(m === 'pan')
-                       this.current = this.pan;
-               else if(m === 'zoom')
-                       this.current = this.zoom;
-               else
-                       this.current = this.auto;
+                       this.event_resize.chart_start_w = this.element.clientWidth;
+                       this.event_resize.chart_start_h = this.element.clientHeight;
+                       this.event_resize.chart_last_w = this.element.clientWidth;
+                       this.event_resize.chart_last_h = this.element.clientHeight;
 
-               this.current.force_update_at = 0;
-               this.current.force_before_ms = null;
-               this.current.force_after_ms = null;
+                       var now = new Date().getTime();
+                       if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
+                               // double click / double tap event
+
+                               // the optimal height of the chart
+                               // showing the entire legend
+                               var optimal = this.event_resize.chart_last_h
+                                               + this.element_legend_childs.content.scrollHeight
+                                               - this.element_legend_childs.content.clientHeight;
+
+                               // if we are not optimal, be optimal
+                               if(this.event_resize.chart_last_h != optimal)
+                                       resizeChartToHeight(optimal.toString() + 'px');
+
+                               // else if we do not have the original height
+                               // reset to the original height
+                               else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
+                                       resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
+                       }
+                       else {
+                               this.event_resize.last = now;
+
+                               // process movement event
+                               document.onmousemove =
+                               document.ontouchmove =
+                               this.element_legend_childs.resize_handler.onmousemove =
+                               this.element_legend_childs.resize_handler.ontouchmove =
+                                       function(e) {
+                                               var y = null;
+
+                                               switch(e.type) {
+                                                       case 'mousemove': y = e.clientY; break;
+                                                       case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
+                                               }
 
-               if(this.debug === true)
-                       this.log('mode set to ' + this.current.name);
-       }
+                                               if(y !== null) {
+                                                       var     newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
 
-       chartState.prototype.updateChartPanOrZoom = function(after, before) {
-               if(before < after) return false;
+                                                       if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
+                                                               resizeChartToHeight(newH.toString() + 'px');
+                                                               that.event_resize.chart_last_h = newH;
+                                                       }
+                                               }
+                                       };
+
+                               // process end event
+                               document.onmouseup = 
+                               document.ontouchend = 
+                               this.element_legend_childs.resize_handler.onmouseup =
+                               this.element_legend_childs.resize_handler.ontouchend =
+                                       function(e) {
+                                               // remove all the hooks
+                                               document.onmouseup =
+                                               document.onmousemove =
+                                               document.ontouchmove =
+                                               document.ontouchend =
+                                               that.element_legend_childs.resize_handler.onmousemove =
+                                               that.element_legend_childs.resize_handler.ontouchmove =
+                                               that.element_legend_childs.resize_handler.onmouseout =
+                                               that.element_legend_childs.resize_handler.onmouseup =
+                                               that.element_legend_childs.resize_handler.ontouchend =
+                                                       null;
+
+                                               // allow auto-refreshes
+                                               NETDATA.options.auto_refresher_stop_until = 0;
+                                       };
+                       }
+               }
 
-               var min_duration = Math.round((this.chartWidth() / 30 * this.chart.update_every * 1000));
 
-               if(this.debug === true)
-                       this.log('requested duration of ' + ((before - after) / 1000).toString() + ' (' + after + ' - ' + before + '), minimum ' + min_duration / 1000);
+               var noDataToShow = function() {
+                       this.legendUpdateDOM();
+                       that.tm.last_autorefreshed = new Date().getTime();
+                       that.data_update_every = 30 * 1000;
+               }
 
-               if((before - after) < min_duration) return false;
+               // ============================================================================================================
+               // PUBLIC FUNCTIONS
 
-               var current_duration = this.data_before - this.data_after;
-               var wanted_duration = before - after;
-               var tolerance = this.data_update_every * 2;
-               var movement = Math.abs(before - this.data_before);
+               this.setMode = function(m) {
+                       if(this.current !== null && this.current.name === m) return;
 
-               if(this.debug === true)
-                       this.log('current duration: ' + current_duration / 1000 + ', wanted duration: ' + wanted_duration / 1000 + ', movement: ' + movement / 1000 + ', tolerance: ' + tolerance / 1000);
+                       if(m === 'auto')
+                               this.current = this.auto;
+                       else if(m === 'pan')
+                               this.current = this.pan;
+                       else if(m === 'zoom')
+                               this.current = this.zoom;
+                       else
+                               this.current = this.auto;
 
-               if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance) {
-                       if(this.debug === true)
-                               this.log('IGNORED');
+                       this.current.force_update_at = 0;
+                       this.current.force_before_ms = null;
+                       this.current.force_after_ms = null;
 
-                       return false;
+                       this.tm.last_mode_switch = new Date().getTime();
                }
 
-               if(this.current.name === 'auto') {
-                       this.setMode('pan');
+               // ----------------------------------------------------------------------------------------------------------------
+               // global selection sync
 
-                       if(this.debug === true)
-                               this.log('updateChartPanOrZoom(): caller did not set proper mode');
+               // prevent to global selection sync for some time
+               this.globalSelectionSyncDelay = function(ms) {
+                       if(NETDATA.options.current.sync_selection === false)
+                               return;
+
+                       if(typeof ms === 'number')
+                               NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
+                       else
+                               NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
                }
 
-               this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
-               this.current.force_after_ms = after;
-               this.current.force_before_ms = before;
-               NETDATA.globalPanAndZoom.setMaster(this, after, before);
-               return true;
-       }
+               // can we globally apply selection sync?
+               this.globalSelectionSyncAbility = function() {
+                       if(NETDATA.options.current.sync_selection === false)
+                               return false;
 
-       chartState.prototype.legendFormatValue = function(value) {
-               if(value === null || value === 'undefined') return '-';
-               if(typeof value !== 'number') return value;
+                       if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
+                               return false;
 
-               var abs = Math.abs(value);
-               if(abs >= 1) return (Math.round(value * 100) / 100).toLocaleString();
-               if(abs >= 0.1) return (Math.round(value * 1000) / 1000).toLocaleString();
-               return (Math.round(value * 10000) / 10000).toLocaleString();
-       }
+                       return true;
+               }
+
+               this.globalSelectionSyncIsMaster = function() {
+                       if(NETDATA.globalSelectionSync.state === this)
+                               return true;
+                       else
+                               return false;
+               }
 
-       chartState.prototype.legendSetLabelValue = function(label, value) {
-               var series = this.element_legend_childs.series[label];
-               if(typeof series === 'undefined') return;
-               if(series.value === null && series.user === null) return;
+               // this chart is the master of the global selection sync
+               this.globalSelectionSyncBeMaster = function() {
+                       // am I the master?
+                       if(this.globalSelectionSyncIsMaster()) {
+                               if(this.debug === true)
+                                       this.log('sync: I am the master already.');
 
-               // if the value has not changed, skip DOM update
-               //if(series.last === value) return;
+                               return;
+                       }
 
-               var s, r;
-               if(typeof value === 'number') {
-                       var v = Math.abs(value);
-                       s = r = this.legendFormatValue(value);
+                       if(NETDATA.globalSelectionSync.state) {
+                               if(this.debug === true)
+                                       this.log('sync: I am not the sync master. Resetting global sync.');
 
-                       if(typeof series.last === 'number') {
-                               if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
-                               else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
-                               else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
+                               this.globalSelectionSyncStop();
                        }
-                       else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
-                       series.last = v;
-               }
-               else {
-                       s = r = value;
-                       series.last = value;
-               }
 
-               if(series.value !== null) series.value.innerHTML = s;
-               if(series.user !== null) series.user.innerHTML = r;
-       }
+                       // become the master
+                       if(this.debug === true)
+                               this.log('sync: becoming sync master.');
 
-       chartState.prototype.legendSetDate = function(ms) {
-               if(typeof ms !== 'number') {
-                       this.legendShowUndefined();
-                       return;
-               }
+                       this.selected = true;
+                       NETDATA.globalSelectionSync.state = this;
 
-               var d = new Date(ms);
+                       // find the all slaves
+                       var targets = NETDATA.options.targets;
+                       var len = targets.length;
+                       while(len--) {
+                               st = targets[len];
 
-               if(this.element_legend_childs.title_date)
-                       this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
+                               if(st === this) {
+                                       if(this.debug === true)
+                                               st.log('sync: not adding me to sync');
+                               }
+                               else if(st.globalSelectionSyncIsEligible()) {
+                                       if(this.debug === true)
+                                               st.log('sync: adding to sync as slave');
 
-               if(this.element_legend_childs.title_time)
-                       this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
+                                       st.globalSelectionSyncBeSlave();
+                               }
+                       }
 
-               if(this.element_legend_childs.title_units)
-                       this.element_legend_childs.title_units.innerHTML = this.chart.units;
-       }
+                       // this.globalSelectionSyncDelay(100);
+               }
 
-       chartState.prototype.legendShowUndefined = function() {
-               if(this.element_legend_childs.title_date)
-                       this.element_legend_childs.title_date.innerHTML = '&nbsp;';
+               // can the chart participate to the global selection sync as a slave?
+               this.globalSelectionSyncIsEligible = function() {
+                       if(this.enabled === true
+                               && this.library !== null
+                               && typeof this.library.setSelection === 'function'
+                               && this.isVisible() === true
+                               && this.chart_created === true)
+                               return true;
 
-               if(this.element_legend_childs.title_time)
-                       this.element_legend_childs.title_time.innerHTML = this.chart.name;
+                       return false;
+               }
 
-               if(this.element_legend_childs.title_units)
-                       this.element_legend_childs.title_units.innerHTML = '&nbsp;';
+               // this chart becomes a slave of the global selection sync
+               this.globalSelectionSyncBeSlave = function() {
+                       if(NETDATA.globalSelectionSync.state !== this)
+                               NETDATA.globalSelectionSync.slaves.push(this);
+               }
 
-               if(this.data && this.element_legend_childs.series !== null) {
-                       var labels = this.data.dimension_names;
-                       var i = labels.length;
-                       while(i--) {
-                               var label = labels[i];
+               // sync all the visible charts to the given time
+               // this is to be called from the chart libraries
+               this.globalSelectionSync = function(t) {
+                       if(this.globalSelectionSyncAbility() === false) {
+                               if(this.debug === true)
+                                       this.log('sync: cannot sync (yet?).');
 
-                               if(typeof label === 'undefined') continue;
-                               if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
-                               this.legendSetLabelValue(label, null);
+                               return;
                        }
-               }
-       }
 
-       chartState.prototype.legendShowLatestValues = function() {
-               if(this.chart === null) return;
-               if(this.selected) return;
-
-               if(this.data === null || this.element_legend_childs.series === null) {
-                       this.legendShowUndefined();
-                       return;
-               }
-
-               var show_undefined = true;
-               if(Math.abs(this.data.last_entry - this.data.before) <= this.data.view_update_every)
-                       show_undefined = false;
-
-               if(show_undefined) {
-                       this.legendShowUndefined();
-                       return;
-               }
+                       if(this.globalSelectionSyncIsMaster() === false) {
+                               if(this.debug === true)
+                                       this.log('sync: trying to be sync master.');
 
-               this.legendSetDate(this.data.before * 1000);
+                               this.globalSelectionSyncBeMaster();
 
-               var labels = this.data.dimension_names;
-               var i = labels.length;
-               while(i--) {
-                       var label = labels[i];
+                               if(this.globalSelectionSyncAbility() === false) {
+                                       if(this.debug === true)
+                                               this.log('sync: cannot sync (yet?).');
 
-                       if(typeof label === 'undefined') continue;
-                       if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
+                                       return;
+                               }
+                       }
 
-                       if(show_undefined)
-                               this.legendSetLabelValue(label, null);
-                       else
-                               this.legendSetLabelValue(label, this.data.view_latest_values[i]);
+                       NETDATA.globalSelectionSync.last_t = t;
+                       $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
+                               st.setSelection(t);
+                       });
                }
-       }
 
-       chartState.prototype.legendReset = function() {
-               this.legendShowLatestValues();
-       }
+               // stop syncing all charts to the given time
+               this.globalSelectionSyncStop = function() {
+                       if(NETDATA.globalSelectionSync.slaves.length) {
+                               if(this.debug === true)
+                                       this.log('sync: cleaning up...');
 
-       // this should be called just ONCE per dimension per chart
-       chartState.prototype._chartDimensionColor = function(label) {
-               if(this.colors === null) this.chartColors();
+                               $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
+                                       if(st === that) {
+                                               if(that.debug === true)
+                                                       st.log('sync: not adding me to sync stop');
+                                       }
+                                       else {
+                                               if(that.debug === true)
+                                                       st.log('sync: removed slave from sync');
 
-               if(typeof this.colors_assigned[label] === 'undefined') {
-                       if(this.colors_available.length === 0) {
-                               for(var i = 0, len = NETDATA.colors.length; i < len ; i++)
-                                       this.colors_available.push(NETDATA.colors[i]);
-                       }
+                                               st.clearSelection();
+                                       }
+                               });
 
-                       this.colors_assigned[label] = this.colors_available.shift();
+                               NETDATA.globalSelectionSync.last_t = 0;
+                               NETDATA.globalSelectionSync.slaves = [];
+                               NETDATA.globalSelectionSync.state = null;
+                       }
 
-                       if(this.debug === true)
-                               this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
-               }
-               else {
-                       if(this.debug === true)
-                               this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
+                       // since we are the sync master, we should not call this.clearSelection()
+                       // dygraphs is taking care of visualizing our selection.
+                       this.selected = false;
+                       this.clearSelection();
                }
 
-               this.colors.push(this.colors_assigned[label]);
-               return this.colors_assigned[label];
-       }
+               this.setSelection = function(t) {
+                       if(typeof this.library.setSelection === 'function') {
+                               if(this.library.setSelection(this, t) === true)
+                                       this.selected = true;
+                               else
+                                       this.selected = false;
+                       }
+                       else this.selected = true;
 
-       chartState.prototype.chartColors = function() {
-               if(this.colors !== null) return this.colors;
+                       if(this.selected === true && this.debug === true)
+                               this.log('selection set to ' + t.toString());
 
-               this.colors = new Array();
-               this.colors_available = new Array();
-               // this.colors_assigned = {};
+                       return this.selected;
+               }
 
-               var c = $(this.element).data('colors');
-               if(typeof c !== 'undefined' && c !== null) {
-                       if(typeof c !== 'string') {
-                               this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
-                       }
-                       else {
-                               c = c.split(' ');
-                               for(var i = 0, len = c.length; i < len ; i++)
-                                       this.colors_available.push(c[i]);
+               this.clearSelection = function() {
+                       if(this.selected === true) {
+                               if(typeof this.library.clearSelection === 'function') {
+                                       if(this.library.clearSelection(this) === true)
+                                               this.selected = false;
+                                       else
+                                               this.selected = true;
+                               }
+                               else this.selected = false;
+                               
+                               if(this.selected === false && this.debug === true)
+                                       this.log('selection cleared');
                        }
+
+                       this.legendReset();
+                       return this.selected;
                }
 
-               // push all the standard colors too
-               for(var i = 0, len = NETDATA.colors.length; i < len ; i++)
-                       this.colors_available.push(NETDATA.colors[i]);
+               // find if a timestamp (ms) is shown in the current chart
+               this.timeIsVisible = function(t) {
+                       if(t >= this.data_after && t <= this.data_before)
+                               return true;
+                       return false;
+               },
 
-               return this.colors;
-       }
+               this.calculateRowForTime = function(t) {
+                       if(this.timeIsVisible(t) === false) return -1;
+                       return Math.floor((t - this.data_after) / this.data_update_every);
+               }
 
-       chartState.prototype.legendUpdateDOM = function() {
-               var needed = false;
+               // ----------------------------------------------------------------------------------------------------------------
 
-               // check that the legend DOM is up to date for the downloaded dimensions
-               if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
-                       // this.log('the legend does not have any series - requesting legend update');
-                       needed = true;
-               }
-               else if(this.data === null) {
-                       // this.log('the chart does not have any data - requesting legend update');
-                       needed = true;
+               // console logging
+               this.log = function(msg) {
+                       console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
                }
-               else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
-                       needed = true;
-               }
-               else {
-                       var labels = this.data.dimension_names.toString();
-                       if(labels !== this.element_legend_childs.series.labels_key) {
-                               needed = true;
 
+               this.pauseChart = function() {
+                       if(this.paused === false) {
                                if(this.debug === true)
-                                       this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
+                                       this.log('paused');
+
+                               this.paused = true;
                        }
                }
 
-               if(needed === false) {
-                       // make sure colors available
-                       this.chartColors();
-
-                       // do we have to update the current values?
-                       // we do this, only when the visible chart is current
-                       if(Math.abs(this.data.last_entry - this.data.before) <= this.data.view_update_every) {
+               this.unpauseChart = function() {
+                       if(this.paused) {
                                if(this.debug === true)
-                                       this.log('chart in running... updating values on legend...');
+                                       this.log('unpaused');
 
-                               //var labels = this.data.dimension_names;
-                               //var i = labels.length;
-                               //while(i--)
-                               //      this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
+                               this.paused = false;
                        }
-                       return;
                }
-               if(this.colors === null) {
-                       // this is the first time we update the chart
-                       // let's assign colors to all dimensions
-                       if(this.library.track_colors() === true)
-                               for(var dim in this.chart.dimensions)
-                                       this._chartDimensionColor(this.chart.dimensions[dim].name);
-               }
-               // we will re-generate the colors for the chart
-               this.colors = null;
 
-               if(this.debug === true)
-                       this.log('updating Legend DOM');
+               this.resetChart = function() {
+                       if(NETDATA.globalPanAndZoom.isMaster(this) && this.isVisible())
+                               NETDATA.globalPanAndZoom.clearMaster();
 
-               // mark all dimensions as invalid
-               this.dimensions_visibility.invalidateAll();
-
-               var self = $(this.element);
-               var genLabel = function(state, parent, name, count) {
-                       var color = state._chartDimensionColor(name);
+                       this.tm.pan_and_zoom_seq = 0;
 
-                       var user_element = null;
-                       var user_id = self.data('show-value-of-' + name + '-at') || null;
-                       if(user_id !== null) {
-                               user_element = document.getElementById(user_id) || null;
-                               if(user_element === null)
-                                       me.log('Cannot find element with id: ' + user_id);
-                       }
+                       this.clearSelection();
 
-                       state.element_legend_childs.series[name] = {
-                               name: document.createElement('span'),
-                               value: document.createElement('span'),
-                               user: user_element,
-                               last: null
-                       };
+                       this.setMode('auto');
+                       this.current.force_update_at = 0;
+                       this.current.force_before_ms = null;
+                       this.current.force_after_ms = null;
+                       this.tm.last_autorefreshed = 0;
+                       this.paused = false;
+                       this.selected = false;
+                       this.enabled = true;
+                       this.debug = false;
 
-                       var label = state.element_legend_childs.series[name];
+                       // do not update the chart here
+                       // or the chart will flip-flop when it is the master
+                       // of a selection sync and another chart becomes
+                       // the new master
+                       if(NETDATA.options.current.sync_pan_and_zoom === false && this.isVisible() === true)
+                               this.updateChart();
+               }
 
-                       // create the dimension visibility tracking for this label
-                       var ds = state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
+               this.updateChartPanOrZoom = function(after, before) {
+                       if(before < after) return false;
 
-                       var rgb = NETDATA.colorHex2Rgb(color);
-                       label.name.innerHTML = '<table class="netdata-legend-name-table-'
-                               + state.chart.chart_type
-                               + '" style="background-color: '
-                               + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
-                               + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
+                       var min_duration = Math.round((this.chartWidth() / 30 * this.chart.update_every * 1000));
 
-                       var text = document.createTextNode(' ' + name);
-                       label.name.appendChild(text);
+                       if(this.debug === true)
+                               this.log('requested duration of ' + ((before - after) / 1000).toString() + ' (' + after + ' - ' + before + '), minimum ' + min_duration / 1000);
 
-                       if(count > 0)
-                               parent.appendChild(document.createElement('br'));
+                       if((before - after) < min_duration) return false;
 
-                       parent.appendChild(label.name);
-                       parent.appendChild(label.value);
-               };
+                       var current_duration = this.data_before - this.data_after;
+                       var wanted_duration = before - after;
+                       var tolerance = this.data_update_every * 2;
+                       var movement = Math.abs(before - this.data_before);
 
-               var content = document.createElement('div');
-
-               if(this.hasLegend()) {
-                       this.element_legend_childs = {
-                               content: content,
-                               resize_handler: document.createElement('div'),
-                               title_date: document.createElement('span'),
-                               title_time: document.createElement('span'),
-                               title_units: document.createElement('span'),
-                               nano: document.createElement('div'),
-                               nano_options: {
-                                       paneClass: 'netdata-legend-series-pane',
-                                       sliderClass: 'netdata-legend-series-slider',
-                                       contentClass: 'netdata-legend-series-content',
-                                       enabledClass: '__enabled',
-                                       flashedClass: '__flashed',
-                                       activeClass: '__active',
-                                       tabIndex: -1,
-                                       alwaysVisible: true,
-                                       sliderMinHeight: 10
-                               },
-                               series: {}
-                       };
+                       if(this.debug === true)
+                               this.log('current duration: ' + current_duration / 1000 + ', wanted duration: ' + wanted_duration / 1000 + ', movement: ' + movement / 1000 + ', tolerance: ' + tolerance / 1000);
 
-                       this.element_legend.innerHTML = '';
+                       if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance) {
+                               if(this.debug === true)
+                                       this.log('IGNORED');
 
-                       this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
-                       this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
-                       this.element.appendChild(this.element_legend_childs.resize_handler);
-                       var self2 = this;
+                               return false;
+                       }
 
-                       // mousedown event
-                       this.element_legend_childs.resize_handler.onmousedown =
-                               function(e) {
-                                       self2.resizeHandler(e);
-                               };
+                       if(this.current.name === 'auto') {
+                               this.setMode('pan');
 
-                       // touchstart event
-                       this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
-                               self2.resizeHandler(e);
-                       }, false);
+                               if(this.debug === true)
+                                       this.log('updateChartPanOrZoom(): caller did not set proper mode');
+                       }
 
-                       this.element_legend_childs.title_date.className += " netdata-legend-title-date";
-                       this.element_legend.appendChild(this.element_legend_childs.title_date);
+                       this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
+                       this.current.force_after_ms = after;
+                       this.current.force_before_ms = before;
+                       NETDATA.globalPanAndZoom.setMaster(this, after, before);
+                       return true;
+               }
 
-                       this.element_legend.appendChild(document.createElement('br'));
+               this.legendFormatValue = function(value) {
+                       if(value === null || value === 'undefined') return '-';
+                       if(typeof value !== 'number') return value;
 
-                       this.element_legend_childs.title_time.className += " netdata-legend-title-time";
-                       this.element_legend.appendChild(this.element_legend_childs.title_time);
+                       var abs = Math.abs(value);
+                       if(abs >= 1) return (Math.round(value * 100) / 100).toLocaleString();
+                       if(abs >= 0.1) return (Math.round(value * 1000) / 1000).toLocaleString();
+                       return (Math.round(value * 10000) / 10000).toLocaleString();
+               }
 
-                       this.element_legend.appendChild(document.createElement('br'));
+               this.legendSetLabelValue = function(label, value) {
+                       var series = this.element_legend_childs.series[label];
+                       if(typeof series === 'undefined') return;
+                       if(series.value === null && series.user === null) return;
 
-                       this.element_legend_childs.title_units.className += " netdata-legend-title-units";
-                       this.element_legend.appendChild(this.element_legend_childs.title_units);
+                       // if the value has not changed, skip DOM update
+                       //if(series.last === value) return;
 
-                       this.element_legend.appendChild(document.createElement('br'));
+                       var s, r;
+                       if(typeof value === 'number') {
+                               var v = Math.abs(value);
+                               s = r = this.legendFormatValue(value);
 
-                       this.element_legend_childs.nano.className = 'netdata-legend-series';
-                       this.element_legend.appendChild(this.element_legend_childs.nano);
+                               if(typeof series.last === 'number') {
+                                       if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
+                                       else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
+                                       else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
+                               }
+                               else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
+                               series.last = v;
+                       }
+                       else {
+                               s = r = value;
+                               series.last = value;
+                       }
 
-                       content.className = 'netdata-legend-series-content';
-                       this.element_legend_childs.nano.appendChild(content);
-               }
-               else {
-                       this.element_legend_childs = {
-                               content: content,
-                               resize_handler: null,
-                               title_date: null,
-                               title_time: null,
-                               title_units: null,
-                               nano: null,
-                               nano_options: null,
-                               series: {}
-                       };
+                       if(series.value !== null) series.value.innerHTML = s;
+                       if(series.user !== null) series.user.innerHTML = r;
                }
 
-               if(this.data) {
-                       this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
-                       if(this.debug === true)
-                               this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
-
-                       for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
-                               genLabel(this, content, this.data.dimension_names[i], i);
-                       }
-               }
-               else {
-                       var tmp = new Array();
-                       for(var dim in this.chart.dimensions) {
-                               tmp.push(this.chart.dimensions[dim].name);
-                               genLabel(this, content, this.chart.dimensions[dim].name, i);
+               this.legendSetDate = function(ms) {
+                       if(typeof ms !== 'number') {
+                               this.legendShowUndefined();
+                               return;
                        }
-                       this.element_legend_childs.series.labels_key = tmp.toString();
-                       if(this.debug === true)
-                               this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
-               }
 
-               // create a hidden div to be used for hidding
-               // the original legend of the chart library
-               var el = document.createElement('div');
-               this.element_legend.appendChild(el);
-               el.style.display = 'none';
+                       var d = new Date(ms);
 
-               this.element_legend_childs.hidden = document.createElement('div');
-               el.appendChild(this.element_legend_childs.hidden);
+                       if(this.element_legend_childs.title_date)
+                               this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
 
-               if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
-                       $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
+                       if(this.element_legend_childs.title_time)
+                               this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
 
-               this.legendShowLatestValues();
-       }
+                       if(this.element_legend_childs.title_units)
+                               this.element_legend_childs.title_units.innerHTML = this.chart.units;
+               }
 
-       chartState.prototype.createChartDOM = function() {
-               if(this.debug === true)
-                       this.log('creating DOM');
-
-               this.element_chart_id = this.library_name + '-' + this.uuid + '-chart';
-               this.element_chart = document.createElement('div');
-               this.element_chart.className += ' netdata-chart' + (this.hasLegend()?'-with-legend-right':'').toString();
-               this.element_chart.className += ' netdata-' + this.library_name + '-chart' + (this.hasLegend()?'-with-legend-right':'').toString();
-               this.element_chart.id = this.element_chart_id;
-               $(this.element_chart).data('netdata-state-object', this);
-               this.element.appendChild(this.element_chart);
-
-               this.element_legend_id = this.library_name + '-' + this.uuid + '-legend';
-               this.element_legend = document.createElement('div');
-               this.element_legend.className += ' netdata-chart-legend';
-               this.element_legend.className += ' netdata-' + this.library_name + '-legend';
-               this.element_legend.id = this.element_legend_id;
-               $(this.element_legend).data('netdata-state-object', this);
-               
-               if(this.hasLegend() === false)
-                       this.element_legend.style.display = 'none';
-               else
-                       this.element.appendChild(this.element_legend);
+               this.legendShowUndefined = function() {
+                       if(this.element_legend_childs.title_date)
+                               this.element_legend_childs.title_date.innerHTML = '&nbsp;';
 
-               this.element_legend_childs.series = null;
-               this.legendUpdateDOM();
+                       if(this.element_legend_childs.title_time)
+                               this.element_legend_childs.title_time.innerHTML = this.chart.name;
 
-               // in case the user has an active global selection sync in place
-               // reset it
-               this.globalSelectionSyncStop();
-               this.dom_created = true;
-       }
+                       if(this.element_legend_childs.title_units)
+                               this.element_legend_childs.title_units.innerHTML = '&nbsp;';
 
-       chartState.prototype.hasLegend = function() {
-               if(typeof this.___hasLegendCache___ !== 'undefined')
-                       return this.___hasLegendCache___;
+                       if(this.data && this.element_legend_childs.series !== null) {
+                               var labels = this.data.dimension_names;
+                               var i = labels.length;
+                               while(i--) {
+                                       var label = labels[i];
 
-               var leg = false;
-               if(this.library && this.library.legend(this) === 'right-side') {
-                       var legend = $(this.element).data('legend') || 'yes';
-                       if(legend === 'yes') leg = true;
+                                       if(typeof label === 'undefined') continue;
+                                       if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
+                                       this.legendSetLabelValue(label, null);
+                               }
+                       }
                }
 
-               this.___hasLegendCache___ = leg;
-               return leg;
-       }
+               this.legendShowLatestValues = function() {
+                       if(this.chart === null) return;
+                       if(this.selected) return;
 
-       chartState.prototype.legendWidth = function() {
-               return (this.hasLegend())?140:0;
-       }
+                       if(this.data === null || this.element_legend_childs.series === null) {
+                               this.legendShowUndefined();
+                               return;
+                       }
 
-       chartState.prototype.legendHeight = function() {
-               return $(this.element).height();
-       }
+                       var show_undefined = true;
+                       if(Math.abs(this.data.last_entry - this.data.before) <= this.data.view_update_every)
+                               show_undefined = false;
 
-       chartState.prototype.chartWidth = function() {
-               return $(this.element).width() - this.legendWidth();
-       }
+                       if(show_undefined) {
+                               this.legendShowUndefined();
+                               return;
+                       }
 
-       chartState.prototype.chartHeight = function() {
-               return $(this.element).height();
-       }
+                       this.legendSetDate(this.data.before * 1000);
 
-       chartState.prototype.chartPixelsPerPoint = function() {
-               // force an options provided detail
-               var px = this.pixels_per_point;
+                       var labels = this.data.dimension_names;
+                       var i = labels.length;
+                       while(i--) {
+                               var label = labels[i];
 
-               if(this.library && px < this.library.pixels_per_point(this))
-                       px = this.library.pixels_per_point(this);
+                               if(typeof label === 'undefined') continue;
+                               if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
 
-               if(px < NETDATA.options.current.pixels_per_point)
-                       px = NETDATA.options.current.pixels_per_point;
+                               if(show_undefined)
+                                       this.legendSetLabelValue(label, null);
+                               else
+                                       this.legendSetLabelValue(label, this.data.view_latest_values[i]);
+                       }
+               }
 
-               return px;
-       }
+               this.legendReset = function() {
+                       this.legendShowLatestValues();
+               }
 
-       chartState.prototype.needsRecreation = function() {
-               return (
-                               this.dom_created === true
-                               && this.chart_created === true
-                               && this.library
-                               && this.library.autoresize() === false
-                               && this.tm.last_resized < NETDATA.options.last_resized
-                       );
-       }
+               // this should be called just ONCE per dimension per chart
+               this._chartDimensionColor = function(label) {
+                       if(this.colors === null) this.chartColors();
 
-       chartState.prototype.resizeChart = function() {
-               if(this.needsRecreation()) {
-                       if(this.debug === true)
-                               this.log('forcing re-generation due to window resize.');
+                       if(typeof this.colors_assigned[label] === 'undefined') {
+                               if(this.colors_available.length === 0) {
+                                       for(var i = 0, len = NETDATA.colors.length; i < len ; i++)
+                                               this.colors_available.push(NETDATA.colors[i]);
+                               }
 
-                       this.destroyChart();
-               }
+                               this.colors_assigned[label] = this.colors_available.shift();
 
-               this.tm.last_resized = new Date().getTime();
-       }
+                               if(this.debug === true)
+                                       this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
+                       }
+                       else {
+                               if(this.debug === true)
+                                       this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
+                       }
 
-       chartState.prototype.chartURL = function() {
-               var before;
-               var after;
-               if(NETDATA.globalPanAndZoom.isActive()) {
-                       after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
-                       before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
-                       this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
+                       this.colors.push(this.colors_assigned[label]);
+                       return this.colors_assigned[label];
                }
-               else {
-                       before = this.current.force_before_ms !== null ? Math.round(this.current.force_before_ms / 1000) : this.before;
-                       after  = this.current.force_after_ms  !== null ? Math.round(this.current.force_after_ms / 1000) : this.after;
-                       this.tm.pan_and_zoom_seq = 0;
+
+               this.chartColors = function() {
+                       if(this.colors !== null) return this.colors;
+
+                       this.colors = new Array();
+                       this.colors_available = new Array();
+                       // this.colors_assigned = {};
+
+                       var c = $(this.element).data('colors');
+                       if(typeof c !== 'undefined' && c !== null) {
+                               if(typeof c !== 'string') {
+                                       this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
+                               }
+                               else {
+                                       c = c.split(' ');
+                                       for(var i = 0, len = c.length; i < len ; i++)
+                                               this.colors_available.push(c[i]);
+                               }
+                       }
+
+                       // push all the standard colors too
+                       for(var i = 0, len = NETDATA.colors.length; i < len ; i++)
+                               this.colors_available.push(NETDATA.colors[i]);
+
+                       return this.colors;
                }
 
-               this.current.requested_after_ms = after * 1000;
-               this.current.requested_before_ms = before * 1000;
+               this.legendUpdateDOM = function() {
+                       var needed = false;
 
-               this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
+                       // check that the legend DOM is up to date for the downloaded dimensions
+                       if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
+                               // this.log('the legend does not have any series - requesting legend update');
+                               needed = true;
+                       }
+                       else if(this.data === null) {
+                               // this.log('the chart does not have any data - requesting legend update');
+                               needed = true;
+                       }
+                       else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
+                               needed = true;
+                       }
+                       else {
+                               var labels = this.data.dimension_names.toString();
+                               if(labels !== this.element_legend_childs.series.labels_key) {
+                                       needed = true;
 
-               // build the data URL
-               this.data_url = this.chart.data_url;
-               this.data_url += "&format="  + this.library.format();
-               this.data_url += "&points="  + this.data_points.toString();
-               this.data_url += "&group="   + this.method;
-               this.data_url += "&options=" + this.library.options();
-               this.data_url += '|jsonwrap';
+                                       if(this.debug === true)
+                                               this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
+                               }
+                       }
 
-               if(NETDATA.options.current.eliminate_zero_dimensions === true)
-                       this.data_url += '|nonzero';
+                       if(needed === false) {
+                               // make sure colors available
+                               this.chartColors();
 
-               if(after)
-                       this.data_url += "&after="  + after.toString();
+                               // do we have to update the current values?
+                               // we do this, only when the visible chart is current
+                               if(Math.abs(this.data.last_entry - this.data.before) <= this.data.view_update_every) {
+                                       if(this.debug === true)
+                                               this.log('chart in running... updating values on legend...');
 
-               if(before)
-                       this.data_url += "&before=" + before.toString();
+                                       //var labels = this.data.dimension_names;
+                                       //var i = labels.length;
+                                       //while(i--)
+                                       //      this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
+                               }
+                               return;
+                       }
+                       if(this.colors === null) {
+                               // this is the first time we update the chart
+                               // let's assign colors to all dimensions
+                               if(this.library.track_colors() === true)
+                                       for(var dim in this.chart.dimensions)
+                                               this._chartDimensionColor(this.chart.dimensions[dim].name);
+                       }
+                       // we will re-generate the colors for the chart
+                       this.colors = null;
 
-               if(this.dimensions)
-                       this.data_url += "&dimensions=" + this.dimensions;
+                       if(this.debug === true)
+                               this.log('updating Legend DOM');
 
-               if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
-                       this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
-       }
+                       // mark all dimensions as invalid
+                       this.dimensions_visibility.invalidateAll();
 
-       chartState.prototype.redrawChart = function() {
-               if(this.data !== null)
-                       this.updateChartWithData(this.data);
-       }
+                       var genLabel = function(state, parent, name, count) {
+                               var color = state._chartDimensionColor(name);
 
-       chartState.prototype.updateChartWithData = function(data) {
-               if(this.debug === true)
-                       this.log('got data from netdata server');
+                               var user_element = null;
+                               var user_id = self.data('show-value-of-' + name + '-at') || null;
+                               if(user_id !== null) {
+                                       user_element = document.getElementById(user_id) || null;
+                                       if(user_element === null)
+                                               me.log('Cannot find element with id: ' + user_id);
+                               }
 
-               this.data = data;
-               this.updates_counter++;
+                               state.element_legend_childs.series[name] = {
+                                       name: document.createElement('span'),
+                                       value: document.createElement('span'),
+                                       user: user_element,
+                                       last: null
+                               };
 
-               var started = new Date().getTime();
-               this.tm.last_updated = started;
+                               var label = state.element_legend_childs.series[name];
 
-               // if the result is JSON, find the latest update-every
-               if(typeof data === 'object') {
-                       if(typeof data.view_update_every !== 'undefined')
-                               this.data_update_every = data.view_update_every * 1000;
+                               // create the dimension visibility tracking for this label
+                               var ds = state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
 
-                       if(typeof data.after !== 'undefined')
-                               this.data_after = data.after * 1000;
+                               var rgb = NETDATA.colorHex2Rgb(color);
+                               label.name.innerHTML = '<table class="netdata-legend-name-table-'
+                                       + state.chart.chart_type
+                                       + '" style="background-color: '
+                                       + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
+                                       + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
 
-                       if(typeof data.before !== 'undefined')
-                               this.data_before = data.before * 1000;
+                               var text = document.createTextNode(' ' + name);
+                               label.name.appendChild(text);
 
-                       if(typeof data.first_entry !== 'undefined')
-                               this.netdata_first = data.first_entry * 1000;
+                               if(count > 0)
+                                       parent.appendChild(document.createElement('br'));
 
-                       if(typeof data.last_entry !== 'undefined')
-                               this.netdata_last = data.last_entry * 1000;
+                               parent.appendChild(label.name);
+                               parent.appendChild(label.value);
+                       };
 
-                       if(typeof data.points !== 'undefined')
-                               this.data_points = data.points;
+                       var content = document.createElement('div');
+
+                       if(this.hasLegend()) {
+                               this.element_legend_childs = {
+                                       content: content,
+                                       resize_handler: document.createElement('div'),
+                                       title_date: document.createElement('span'),
+                                       title_time: document.createElement('span'),
+                                       title_units: document.createElement('span'),
+                                       nano: document.createElement('div'),
+                                       nano_options: {
+                                               paneClass: 'netdata-legend-series-pane',
+                                               sliderClass: 'netdata-legend-series-slider',
+                                               contentClass: 'netdata-legend-series-content',
+                                               enabledClass: '__enabled',
+                                               flashedClass: '__flashed',
+                                               activeClass: '__active',
+                                               tabIndex: -1,
+                                               alwaysVisible: true,
+                                               sliderMinHeight: 10
+                                       },
+                                       series: {}
+                               };
 
-                       data.state = this;
-               }
+                               this.element_legend.innerHTML = '';
 
-               if(this.debug === true) {
-                       this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
+                               this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
+                               this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
+                               this.element.appendChild(this.element_legend_childs.resize_handler);
 
-                       if(this.current.force_after_ms)
-                               this.log('STATUS: forced   : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
-                       else
-                               this.log('STATUS: forced: unset');
+                               // mousedown event
+                               this.element_legend_childs.resize_handler.onmousedown =
+                                       function(e) {
+                                               that.resizeHandler(e);
+                                       };
 
-                       this.log('STATUS: requested: ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
-                       this.log('STATUS: rendered : ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
-                       this.log('STATUS: points   : ' + (this.data_points).toString());
-               }
+                               // touchstart event
+                               this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
+                                       that.resizeHandler(e);
+                               }, false);
 
-               if(this.data_points === 0) {
-                       this.noData();
-                       return;
-               }
+                               this.element_legend_childs.title_date.className += " netdata-legend-title-date";
+                               this.element_legend.appendChild(this.element_legend_childs.title_date);
 
-               // this may force the chart to be re-created
-               this.resizeChart();
+                               this.element_legend.appendChild(document.createElement('br'));
 
-               if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
-                       if(this.debug === true)
-                               this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
+                               this.element_legend_childs.title_time.className += " netdata-legend-title-time";
+                               this.element_legend.appendChild(this.element_legend_childs.title_time);
 
-                       this.chart_created = false;
-               }
+                               this.element_legend.appendChild(document.createElement('br'));
 
-               if(this.chart_created === true
-                       && this.dom_created === true
-                       && typeof this.library.update === 'function') {
+                               this.element_legend_childs.title_units.className += " netdata-legend-title-units";
+                               this.element_legend.appendChild(this.element_legend_childs.title_units);
 
-                       if(this.debug === true)
-                               this.log('updating chart...');
+                               this.element_legend.appendChild(document.createElement('br'));
 
-                       // check and update the legend
-                       this.legendUpdateDOM();
+                               this.element_legend_childs.nano.className = 'netdata-legend-series';
+                               this.element_legend.appendChild(this.element_legend_childs.nano);
 
-                       this.updates_since_last_creation++;
-                       if(NETDATA.options.debug.chart_errors === true) {
-                               this.library.update(this, data);
+                               content.className = 'netdata-legend-series-content';
+                               this.element_legend_childs.nano.appendChild(content);
                        }
                        else {
-                               try {
-                                       this.library.update(this, data);
-                               }
-                               catch(err) {
-                                       this.error('chart failed to be updated as ' + this.library_name);
-                               }
+                               this.element_legend_childs = {
+                                       content: content,
+                                       resize_handler: null,
+                                       title_date: null,
+                                       title_time: null,
+                                       title_units: null,
+                                       nano: null,
+                                       nano_options: null,
+                                       series: {}
+                               };
                        }
-               }
-               else {
-                       if(this.debug === true)
-                               this.log('creating chart...');
 
-                       this.createChartDOM();
-                       this.updates_since_last_creation = 0;
+                       if(this.data) {
+                               this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
+                               if(this.debug === true)
+                                       this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
 
-                       if(NETDATA.options.debug.chart_errors === true) {
-                               this.library.create(this, data);
-                               this.chart_created = true;
+                               for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
+                                       genLabel(this, content, this.data.dimension_names[i], i);
+                               }
                        }
                        else {
-                               try {
-                                       this.library.create(this, data);
-                                       this.chart_created = true;
-                               }
-                               catch(err) {
-                                       this.error('chart failed to be created as ' + this.library_name);
+                               var tmp = new Array();
+                               for(var dim in this.chart.dimensions) {
+                                       tmp.push(this.chart.dimensions[dim].name);
+                                       genLabel(this, content, this.chart.dimensions[dim].name, i);
                                }
+                               this.element_legend_childs.series.labels_key = tmp.toString();
+                               if(this.debug === true)
+                                       this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
                        }
-               }
-               // this.legendShowLatestValues();
-
-               // update the performance counters
-               var now = new Date().getTime();
-
-               // don't update last_autorefreshed if this chart is
-               // forced to be updated with global PanAndZoom
-               if(NETDATA.globalPanAndZoom.isActive())
-                       this.tm.last_autorefreshed = 0;
-               else {
-                       if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
-                               this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
-                       else
-                               this.tm.last_autorefreshed = now;
-               }
 
-               this.refresh_dt_ms = now - started;
-               NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
+                       // create a hidden div to be used for hidding
+                       // the original legend of the chart library
+                       var el = document.createElement('div');
+                       if(this.element_legend !== null)
+                               this.element_legend.appendChild(el);
+                       el.style.display = 'none';
 
-               if(this.refresh_dt_element)
-                       this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
-       }
+                       this.element_legend_childs.hidden = document.createElement('div');
+                       el.appendChild(this.element_legend_childs.hidden);
 
-       chartState.prototype.updateChart = function(callback) {
-               // due to late initialization of charts and libraries
-               // we need to check this too
-               if(this.enabled === false) {
-                       if(this.debug === true)
-                               this.log('I am not enabled');
+                       if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
+                               $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
 
-                       if(typeof callback === 'function') callback();
-                       return false;
+                       this.legendShowLatestValues();
                }
 
-               if(this.chart === null) {
-                       var self = this;
-                       this.getChart(function() { self.updateChart(callback); });
-                       return;
-               }
+               this.hasLegend = function() {
+                       if(typeof this.___hasLegendCache___ !== 'undefined')
+                               return this.___hasLegendCache___;
 
-               if(this.library.initialized === false) {
-                       if(this.library.enabled === true) {
-                               var self = this;
-                               this.library.initialize(function() { self.updateChart(callback); });
-                               return;
+                       var leg = false;
+                       if(this.library && this.library.legend(this) === 'right-side') {
+                               var legend = $(this.element).data('legend') || 'yes';
+                               if(legend === 'yes') leg = true;
                        }
-                       else {
-                               this.error('chart library "' + this.library_name + '" is not available.');
-                               if(typeof callback === 'function') callback();
-                               return false;
-                       }
-               }
-
-               this.clearSelection();
-               this.chartURL();
-               this.showLoading();
-
-               if(this.debug === true)
-                       this.log('updating from ' + this.data_url);
-
-               var self = this;
-               this.xhr = $.ajax( {
-                       url: this.data_url,
-                       crossDomain: NETDATA.options.crossDomainAjax,
-                       cache: false,
-                       async: true
-               })
-               .success(function(data) {
-                       self.hideLoading();
 
-                       if(self.debug === true)
-                               self.log('data received. updating chart.');
-
-                       self.updateChartWithData(data);
-               })
-               .fail(function() {
-                       self.hideLoading();
-                       self.error('data download failed for url: ' + self.data_url);
-               })
-               .always(function() {
-                       self.hideLoading();
-                       if(typeof callback === 'function') callback();
-               });
-       }
-
-       chartState.prototype.destroyChart = function() {
-               if(this.debug === true)
-                       this.log('destroying chart');
-
-               if(this.element_message !== null) {
-                       this.element_message.innerHTML = '';
-                       this.element_message = null;
+                       this.___hasLegendCache___ = leg;
+                       return leg;
                }
 
-               if(this.element_loading !== null) {
-                       this.element_loading.innerHTML = '';
-                       this.element_loading = null;
+               this.legendWidth = function() {
+                       return (this.hasLegend())?140:0;
                }
 
-               if(this.element_legend !== null) {
-                       this.element_legend.innerHTML = '';
-                       this.element_legend = null;
+               this.legendHeight = function() {
+                       return $(this.element).height();
                }
 
-               if(this.element_chart !== null) {
-                       this.element_chart.innerHTML = '';
-                       this.element_chart = null;
+               this.chartWidth = function() {
+                       return $(this.element).width() - this.legendWidth();
                }
 
-               this.element_legend_childs = {
-                       hidden: null,
-                       title_date: null,
-                       title_time: null,
-                       title_units: null,
-                       nano: null,
-                       nano_options: null,
-                       series: null
-               };
-
-               this.element.innerHTML = '';
-               this.refresh_dt_element = null;
-
-               this.dom_created = false;
-               this.chart_created = false;
-               this.paused = false;
-               this.selected = false;
-               this.updates_counter = 0;
-               this.updates_since_last_creation = 0;
-               this.tm.pan_and_zoom_seq = 0;
-               this.tm.last_resized = 0;
-               this.tm.last_visible_check = 0;
-               this.tm.last_hidden = 0;
-               this.tm.last_unhidden = 0;
-               this.tm.last_autorefreshed = 0;
-
-               this.data = null;
-               this.data_points = 0;
-               this.data_after = 0;
-               this.data_before = 0;
-               this.data_update_every = 0;
-               this.netdata_first = 0;
-               this.netdata_last = 0;
-               if(this.current !== null) {
-                       this.current.force_update_at = 0;
-                       this.current.force_after_ms = null;
-                       this.current.force_before_ms = null;
-                       this.current.requested_after_ms = null;
-                       this.current.requested_before_ms = null;
+               this.chartHeight = function() {
+                       return $(this.element).height();
                }
-               this.init();
-       }
 
-       chartState.prototype.unhideChart = function() {
-               if(typeof this.___isHidden___ !== 'undefined' && this.enabled === true) {
-                       if(this.debug === true)
-                               this.log('unhiding chart');
+               this.chartPixelsPerPoint = function() {
+                       // force an options provided detail
+                       var px = this.pixels_per_point;
 
-                       this.element_message.style.display = 'none';
-                       if(this.element_chart !== null) this.element_chart.style.display = 'inline-block';
-                       if(this.element_legend !== null) this.element_legend.style.display = 'inline-block';
-                       if(this.element_loading !== null) this.element_loading.style.display = 'none';
-                       this.___isHidden___ = undefined;
-                       this.element_message.innerHTML = 'chart ' + this.id + ' is visible now';
+                       if(this.library && px < this.library.pixels_per_point(this))
+                               px = this.library.pixels_per_point(this);
 
-                       // refresh the scrolbar
-                       if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
-                               $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
+                       if(px < NETDATA.options.current.pixels_per_point)
+                               px = NETDATA.options.current.pixels_per_point;
 
-                       this.tm.last_unhidden = new Date().getTime();
+                       return px;
                }
-       }
 
-       chartState.prototype.hideChart = function() {
-               if(typeof this.___isHidden___ === 'undefined' && this.enabled === true) {
-                       if(NETDATA.options.current.destroy_on_hide === true)
-                               this.destroyChart();
-
-                       if(this.debug === true)
-                               this.log('hiding chart');
-
-                       this.element_message.style.display = 'inline-block';
-                       if(this.element_chart !== null) this.element_chart.style.display = 'none';
-                       if(this.element_legend !== null) this.element_legend.style.display = 'none';
-                       if(this.element_loading !== null) this.element_loading.style.display = 'none';
-                       this.___isHidden___ = true;
-                       this.___showsLoading___ = undefined;
-                       this.element_message.innerHTML = 'chart ' + this.id + ' is hidden to speed up the browser';
-                       this.tm.last_hidden = new Date().getTime();
+               this.needsRecreation = function() {
+                       return (
+                                       this.chart_created === true
+                                       && this.library
+                                       && this.library.autoresize() === false
+                                       && this.tm.last_resized < NETDATA.options.last_resized
+                               );
                }
-       }
 
-       chartState.prototype.hideLoading = function() {
-               if(typeof this.___showsLoading___ !== 'undefined' && this.enabled === true) {
-                       if(this.debug === true)
-                               this.log('hide loading...');
-
-                       this.element_message.style.display = 'none';
-                       if(this.element_chart !== null) this.element_chart.style.display = 'inline-block';
-                       if(this.element_legend !== null) this.element_legend.style.display = 'inline-block';
-                       if(this.element_loading !== null) this.element_loading.style.display = 'none';
-                       this.___showsLoading___ = undefined;
-                       this.element_loading.innerHTML = 'chart ' + this.id + ' finished loading!';
-                       this.tm.last_unhidden = new Date().getTime();
-               }
-       }
+               this.chartURL = function() {
+                       var before;
+                       var after;
+                       if(NETDATA.globalPanAndZoom.isActive()) {
+                               after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
+                               before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
+                               this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
+                       }
+                       else {
+                               before = this.current.force_before_ms !== null ? Math.round(this.current.force_before_ms / 1000) : this.before;
+                               after  = this.current.force_after_ms  !== null ? Math.round(this.current.force_after_ms / 1000) : this.after;
+                               this.tm.pan_and_zoom_seq = 0;
+                       }
 
-       chartState.prototype.showLoading = function() {
-               if(typeof this.___showsLoading___ === 'undefined' && this.chart_created === false && this.enabled === true) {
-                       if(this.debug === true)
-                               this.log('show loading...');
+                       this.requested_after = after * 1000;
+                       this.requested_before = before * 1000;
 
-                       this.element_message.style.display = 'none';
-                       if(this.element_chart !== null) this.element_chart.style.display = 'none';
-                       if(this.element_legend !== null) this.element_legend.style.display = 'none';
-                       if(this.element_loading !== null) this.element_loading.style.display = 'inline-block';
-                       this.___showsLoading___ = true;
-                       this.___isHidden___ = undefined;
-                       this.element_loading.innerHTML = 'chart ' + this.id + ' is loading...';
-                       this.tm.last_hidden = new Date().getTime();
-               }
-       }
+                       this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
 
-       chartState.prototype.isVisible = function() {
-               // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
+                       // build the data URL
+                       this.data_url = this.chart.data_url;
+                       this.data_url += "&format="  + this.library.format();
+                       this.data_url += "&points="  + this.data_points.toString();
+                       this.data_url += "&group="   + this.method;
+                       this.data_url += "&options=" + this.library.options();
+                       this.data_url += '|jsonwrap';
 
-               // caching - we do not evaluate the charts visibility
-               // if the page has not been scrolled since the last check
-               if(this.tm.last_visible_check > NETDATA.options.last_page_scroll) {
-                       if(this.debug === true)
-                               this.log('isVisible: ' + this.___isVisible___);
+                       if(NETDATA.options.current.eliminate_zero_dimensions === true)
+                               this.data_url += '|nonzero';
 
-                       return this.___isVisible___;
-               }
+                       if(after)
+                               this.data_url += "&after="  + after.toString();
 
-               this.tm.last_visible_check = new Date().getTime();
+                       if(before)
+                               this.data_url += "&before=" + before.toString();
 
-               var wh = window.innerHeight;
-               var x = this.element.getBoundingClientRect();
-               var ret = 0;
-               var tolerance = 0;
+                       if(this.dimensions)
+                               this.data_url += "&dimensions=" + this.dimensions;
 
-               if(x.top < 0 && -x.top > x.height) {
-                       // the chart is entirely above
-                       ret = -x.top - x.height;
+                       if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
+                               this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
                }
-               else if(x.top > wh) {
-                       // the chart is entirely below
-                       ret = x.top - wh;
+
+               this.redrawChart = function() {
+                       if(this.data !== null)
+                               this.updateChartWithData(this.data);
                }
 
-               if(ret > tolerance) {
-                       // the chart is too far
-                       this.___isVisible___ = false;
-                       if(this.chart_created === true) this.hideChart();
-                       
+               this.updateChartWithData = function(data) {
                        if(this.debug === true)
-                               this.log('isVisible: ' + this.___isVisible___);
+                               this.log('got data from netdata server');
 
-                       return this.___isVisible___;
-               }
-               else {
-                       // the chart is inside or very close
-                       this.___isVisible___ = true;
-                       this.unhideChart();
-                       
-                       if(this.debug === true)
-                               this.log('isVisible: ' + this.___isVisible___);
+                       // this may force the chart to be re-created
+                       resizeChart();
 
-                       return this.___isVisible___;
-               }
-       }
+                       this.data = data;
+                       this.updates_counter++;
 
-       chartState.prototype.isAutoRefreshed = function() {
-               return (this.current.autorefresh);
-       }
+                       var started = new Date().getTime();
 
-       chartState.prototype.canBeAutoRefreshed = function() {
-               now = new Date().getTime();
+                       // if the result is JSON, find the latest update-every
+                       if(typeof data === 'object') {
+                               if(typeof data.view_update_every !== 'undefined')
+                                       this.data_update_every = data.view_update_every * 1000;
 
-               if(this.enabled === false) {
-                       if(this.debug === true)
-                               this.log('I am not enabled');
+                               if(typeof data.after !== 'undefined')
+                                       this.data_after = data.after * 1000;
 
-                       return false;
-               }
+                               if(typeof data.before !== 'undefined')
+                                       this.data_before = data.before * 1000;
 
-               if(this.library === null || this.library.enabled === false) {
-                       this.error('charting library "' + this.library_name + '" is not available');
-                       if(this.debug === true)
-                               this.log('My chart library ' + this.library_name + ' is not available');
+                               if(typeof data.first_entry !== 'undefined')
+                                       this.netdata_first = data.first_entry * 1000;
 
-                       return false;
-               }
+                               if(typeof data.last_entry !== 'undefined')
+                                       this.netdata_last = data.last_entry * 1000;
 
-               if(this.isVisible() === false) {
-                       if(NETDATA.options.debug.visibility === true || this.debug === true)
-                               this.log('I am not visible');
+                               if(typeof data.points !== 'undefined')
+                                       this.data_points = data.points;
 
-                       return false;
-               }
-               
-               if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
-                       if(this.debug === true)
-                               this.log('timed force update detected - allowing this update');
+                               data.state = this;
+                       }
 
-                       this.current.force_update_at = 0;
-                       return true;
-               }
+                       if(this.debug === true) {
+                               this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
+
+                               if(this.current.force_after_ms)
+                                       this.log('STATUS: forced   : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
+                               else
+                                       this.log('STATUS: forced: unset');
 
-               if(this.isAutoRefreshed() === true) {
-                       // allow the first update, even if the page is not visible
-                       if(this.updates_counter && NETDATA.options.page_is_visible === false) {
-                               if(NETDATA.options.debug.focus === true || this.debug === true)
-                                       this.log('canBeAutoRefreshed(): page does not have focus');
+                               this.log('STATUS: requested: ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
+                               this.log('STATUS: rendered : ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
+                               this.log('STATUS: points   : ' + (this.data_points).toString());
+                       }
 
-                               return false;
+                       if(this.data_points === 0) {
+                               noDataToShow();
+                               return;
                        }
 
-                       if(this.needsRecreation() === true) {
+                       if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
                                if(this.debug === true)
-                                       this.log('canBeAutoRefreshed(): needs re-creation.');
+                                       this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
 
-                               return true;
+                               this.chart_created = false;
                        }
 
-                       // options valid only for autoRefresh()
-                       if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
-                               if(NETDATA.globalPanAndZoom.isActive()) {
-                                       if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
-                                               if(this.debug === true)
-                                                       this.log('canBeAutoRefreshed(): global panning: I need an update.');
+                       // check and update the legend
+                       this.legendUpdateDOM();
 
-                                               return true;
-                                       }
-                                       else {
-                                               if(this.debug === true)
-                                                       this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
+                       if(this.chart_created === true
+                               && typeof this.library.update === 'function') {
 
-                                               return false;
+                               if(this.debug === true)
+                                       this.log('updating chart...');
+
+                               this.updates_since_last_creation++;
+                               if(NETDATA.options.debug.chart_errors === true) {
+                                       this.library.update(this, data);
+                               }
+                               else {
+                                       try {
+                                               this.library.update(this, data);
+                                       }
+                                       catch(err) {
+                                               error('chart failed to be updated as ' + this.library_name);
                                        }
                                }
+                       }
+                       else {
+                               if(this.debug === true)
+                                       this.log('creating chart...');
 
-                               if(this.selected === true) {
-                                       if(this.debug === true)
-                                               this.log('canBeAutoRefreshed(): I have a selection in place.');
-
-                                       return false;
+                               if(NETDATA.options.debug.chart_errors === true) {
+                                       this.library.create(this, data);
+                                       this.chart_created = true;
+                                       this.updates_since_last_creation = 0;
                                }
-
-                               if(this.paused === true) {
-                                       if(this.debug === true)
-                                               this.log('canBeAutoRefreshed(): I am paused.');
-
-                                       return false;
+                               else {
+                                       try {
+                                               this.library.create(this, data);
+                                               this.chart_created = true;
+                                               this.updates_since_last_creation = 0;
+                                       }
+                                       catch(err) {
+                                               error('chart failed to be created as ' + this.library_name);
+                                       }
                                }
+                       }
+                       hideMessage();
+                       NETDATA.globalSelectionSync.stop();
 
-                               if(now - this.tm.last_autorefreshed >= this.data_update_every) {
-                                       if(this.debug === true)
-                                               this.log('canBeAutoRefreshed(): It is time to update me.');
+                       // this.legendShowLatestValues();
 
-                                       return true;
-                               }
+                       // update the performance counters
+                       var now = new Date().getTime();
+                       this.tm.last_updated = now;
+
+                       // don't update last_autorefreshed if this chart is
+                       // forced to be updated with global PanAndZoom
+                       if(NETDATA.globalPanAndZoom.isActive())
+                               this.tm.last_autorefreshed = 0;
+                       else {
+                               if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
+                                       this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
+                               else
+                                       this.tm.last_autorefreshed = now;
                        }
-               }
 
-               return false;
-       }
+                       this.refresh_dt_ms = now - started;
+                       NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
 
-       chartState.prototype.autoRefresh = function(callback) {
-               if(this.canBeAutoRefreshed() === true) {
-                       this.updateChart(callback);
-               }
-               else {
-                       if(typeof callback !== 'undefined')
-                               callback();
+                       if(this.refresh_dt_element !== null)
+                               this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
                }
-       }
 
-       chartState.prototype._defaultsFromDownloadedChart = function(chart) {
-               this.chart = chart;
-               this.chart_url = chart.url;
-               this.data_update_every = chart.update_every * 1000;
-               this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
-               this.tm.last_info_downloaded = new Date().getTime();
-       }
+               this.updateChart = function(callback) {
+                       // due to late initialization of charts and libraries
+                       // we need to check this too
+                       if(this.enabled === false) {
+                               if(this.debug === true)
+                                       this.log('I am not enabled');
 
-       // fetch the chart description from the netdata server
-       chartState.prototype.getChart = function(callback) {
-               this.chart = NETDATA.chartRegistry.get(this.host, this.id);
-               if(this.chart) {
-                       this._defaultsFromDownloadedChart(this.chart);
-                       if(typeof callback === 'function') callback();
-               }
-               else {
-                       this.chart_url = this.host + "/api/v1/chart?chart=" + this.id;
+                               if(typeof callback === 'function') callback();
+                               return false;
+                       }
 
-                       if(this.debug === true)
-                               this.log('downloading ' + this.chart_url);
+                       if(this.chart === null) {
+                               this.getChart(function() { that.updateChart(callback); });
+                               return;
+                       }
 
-                       var self = this;
+                       if(this.library.initialized === false) {
+                               if(this.library.enabled === true) {
+                                       this.library.initialize(function() { that.updateChart(callback); });
+                                       return;
+                               }
+                               else {
+                                       error('chart library "' + this.library_name + '" is not available.');
+                                       if(typeof callback === 'function') callback();
+                                       return false;
+                               }
+                       }
+
+                       this.clearSelection();
+                       this.chartURL();
 
-                       $.ajax( {
-                               url:  this.chart_url,
+                       if(this.debug === true)
+                               this.log('updating from ' + this.data_url);
+
+                       this.xhr = $.ajax( {
+                               url: this.data_url,
                                crossDomain: NETDATA.options.crossDomainAjax,
                                cache: false,
                                async: true
                        })
-                       .done(function(chart) {
-                               chart.url = self.chart_url;
-                               chart.data_url = (self.host + chart.data_url);
-                               self._defaultsFromDownloadedChart(chart);
-                               NETDATA.chartRegistry.add(self.host, self.id, chart);
+                       .success(function(data) {
+                               if(that.debug === true)
+                                       that.log('data received. updating chart.');
+
+                               that.updateChartWithData(data);
                        })
                        .fail(function() {
-                               NETDATA.error(404, self.chart_url);
-                               self.error('chart not found on url "' + self.chart_url + '"');
+                               error('data download failed for url: ' + that.data_url);
                        })
                        .always(function() {
                                if(typeof callback === 'function') callback();
                        });
                }
-       }
 
-       // resize the chart to its real dimensions
-       // as given by the caller
-       chartState.prototype.sizeChart = function() {
-               if(this.hasLegend() === true)
-                       this.element.className += " netdata-container-with-legend";
-               else
-                       this.element.className += " netdata-container";
+               this.isVisible = function() {
+                       // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
 
-               if(this.debug === true)
-                       this.log('sizing element');
+                       // caching - we do not evaluate the charts visibility
+                       // if the page has not been scrolled since the last check
+                       if(this.tm.last_visible_check > NETDATA.options.last_page_scroll) {
+                               if(this.debug === true)
+                                       this.log('isVisible: ' + this.___isVisible___);
 
-               if(this.width !== 0)
-                       $(this.element).css('width', this.width);
+                               return this.___isVisible___;
+                       }
 
-               if(this.height !== 0)
-                       $(this.element).css('height', this.height);
+                       this.tm.last_visible_check = new Date().getTime();
 
-               if(NETDATA.chartDefaults.min_width !== null)
-                       $(this.element).css('min-width', NETDATA.chartDefaults.min_width);
-       }
+                       var wh = window.innerHeight;
+                       var x = this.element.getBoundingClientRect();
+                       var ret = 0;
+                       var tolerance = 0;
 
-       chartState.prototype.noData = function() {
-               if(this.dom_created === false)
-                       this.createChartDOM();
+                       if(x.top < 0 && -x.top > x.height) {
+                               // the chart is entirely above
+                               ret = -x.top - x.height;
+                       }
+                       else if(x.top > wh) {
+                               // the chart is entirely below
+                               ret = x.top - wh;
+                       }
 
-               this.tm.last_autorefreshed = new Date().getTime();
-               this.data_update_every = 30 * 1000;
-       }
+                       if(ret > tolerance) {
+                               // the chart is too far
 
-       // show a message in the chart
-       chartState.prototype.message = function(type, msg) {
-               this.hideChart();
-               this.element_message.innerHTML = msg;
+                               hideChart();
+                               this.___isVisible___ = false;
+                               
+                               if(this.debug === true)
+                                       this.log('isVisible: ' + this.___isVisible___);
 
-               if(this.debug === null)
-                       this.log(msg);
-       }
+                               return this.___isVisible___;
+                       }
+                       else {
+                               // the chart is inside or very close
 
-       // show an error on the chart and stop it forever
-       chartState.prototype.error = function(msg) {
-               this.message('error', this.id + ': ' + msg);
-               this.enabled = false;
-       }
+                               unhideChart();
+                               this.___isVisible___ = true;
+                               
+                               if(this.debug === true)
+                                       this.log('isVisible: ' + this.___isVisible___);
 
-       // show a message indicating the chart is loading
-       chartState.prototype.info = function(msg) {
-               this.message('info', this.id + ': ' + msg);
-       }
+                               return this.___isVisible___;
+                       }
+               }
 
-       chartState.prototype.init = function() {
-               this.element.innerHTML = '';
+               this.isAutoRefreshed = function() {
+                       return (this.current.autorefresh);
+               }
 
-               this.element_message = document.createElement('div');
-               this.element_message.className += ' netdata-message';
-               this.element.appendChild(this.element_message);
+               this.canBeAutoRefreshed = function() {
+                       now = new Date().getTime();
 
-               this.element_loading = document.createElement('div');
-               this.element_loading.className += ' netdata-chart-is-loading';
-               this.element.appendChild(this.element_loading);
+                       if(this.enabled === false) {
+                               if(this.debug === true)
+                                       this.log('I am not enabled');
 
-               if(this.debug === null)
-                       this.log('created');
+                               return false;
+                       }
 
-               // make sure the host does not end with /
-               // all netdata API requests use absolute paths
-               while(this.host.slice(-1) === '/')
-                       this.host = this.host.substring(0, this.host.length - 1);
+                       if(this.library === null || this.library.enabled === false) {
+                               error('charting library "' + this.library_name + '" is not available');
+                               if(this.debug === true)
+                                       this.log('My chart library ' + this.library_name + ' is not available');
 
-               // check the requested library is available
-               // we don't initialize it here - it will be initialized when
-               // this chart will be first used
-               if(typeof NETDATA.chartLibraries[this.library_name] === 'undefined') {
-                       NETDATA.error(402, this.library_name);
-                       this.error('chart library "' + this.library_name + '" is not found');
+                               return false;
+                       }
+
+                       if(this.isVisible() === false) {
+                               if(NETDATA.options.debug.visibility === true || this.debug === true)
+                                       this.log('I am not visible');
+
+                               return false;
+                       }
+                       
+                       if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
+                               if(this.debug === true)
+                                       this.log('timed force update detected - allowing this update');
+
+                               this.current.force_update_at = 0;
+                               return true;
+                       }
+
+                       if(this.isAutoRefreshed() === true) {
+                               // allow the first update, even if the page is not visible
+                               if(this.updates_counter && NETDATA.options.page_is_visible === false) {
+                                       if(NETDATA.options.debug.focus === true || this.debug === true)
+                                               this.log('canBeAutoRefreshed(): page does not have focus');
+
+                                       return false;
+                               }
+
+                               if(this.needsRecreation() === true) {
+                                       if(this.debug === true)
+                                               this.log('canBeAutoRefreshed(): needs re-creation.');
+
+                                       return true;
+                               }
+
+                               // options valid only for autoRefresh()
+                               if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
+                                       if(NETDATA.globalPanAndZoom.isActive()) {
+                                               if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
+                                                       if(this.debug === true)
+                                                               this.log('canBeAutoRefreshed(): global panning: I need an update.');
+
+                                                       return true;
+                                               }
+                                               else {
+                                                       if(this.debug === true)
+                                                               this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
+
+                                                       return false;
+                                               }
+                                       }
+
+                                       if(this.selected === true) {
+                                               if(this.debug === true)
+                                                       this.log('canBeAutoRefreshed(): I have a selection in place.');
+
+                                               return false;
+                                       }
+
+                                       if(this.paused === true) {
+                                               if(this.debug === true)
+                                                       this.log('canBeAutoRefreshed(): I am paused.');
+
+                                               return false;
+                                       }
+
+                                       if(now - this.tm.last_autorefreshed >= this.data_update_every) {
+                                               if(this.debug === true)
+                                                       this.log('canBeAutoRefreshed(): It is time to update me.');
+
+                                               return true;
+                                       }
+                               }
+                       }
+
+                       return false;
                }
-               else if(NETDATA.chartLibraries[this.library_name].enabled === false) {
-                       NETDATA.error(403, this.library_name);
-                       this.error('chart library "' + this.library_name + '" is not enabled');
+
+               this.autoRefresh = function(callback) {
+                       if(this.canBeAutoRefreshed() === true) {
+                               this.updateChart(callback);
+                       }
+                       else {
+                               if(typeof callback !== 'undefined')
+                                       callback();
+                       }
                }
-               else
-                       this.library = NETDATA.chartLibraries[this.library_name];
 
-               this.sizeChart();
+               this._defaultsFromDownloadedChart = function(chart) {
+                       this.chart = chart;
+                       this.chart_url = chart.url;
+                       this.data_update_every = chart.update_every * 1000;
+                       this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
+                       this.tm.last_info_downloaded = new Date().getTime();
+               }
 
-               // if we need to report the rendering speed
-               // find the element that needs to be updated
-               if(this.refresh_dt_element_name)
-                       this.refresh_dt_element = document.getElementById(this.refresh_dt_element_name) || null;
+               // fetch the chart description from the netdata server
+               this.getChart = function(callback) {
+                       this.chart = NETDATA.chartRegistry.get(this.host, this.id);
+                       if(this.chart) {
+                               this._defaultsFromDownloadedChart(this.chart);
+                               if(typeof callback === 'function') callback();
+                       }
+                       else {
+                               this.chart_url = this.host + "/api/v1/chart?chart=" + this.id;
+
+                               if(this.debug === true)
+                                       this.log('downloading ' + this.chart_url);
+
+                               $.ajax( {
+                                       url:  this.chart_url,
+                                       crossDomain: NETDATA.options.crossDomainAjax,
+                                       cache: false,
+                                       async: true
+                               })
+                               .done(function(chart) {
+                                       chart.url = that.chart_url;
+                                       chart.data_url = (that.host + chart.data_url);
+                                       that._defaultsFromDownloadedChart(chart);
+                                       NETDATA.chartRegistry.add(that.host, that.id, chart);
+                               })
+                               .fail(function() {
+                                       NETDATA.error(404, that.chart_url);
+                                       error('chart not found on url "' + that.chart_url + '"');
+                               })
+                               .always(function() {
+                                       if(typeof callback === 'function') callback();
+                               });
+                       }
+               }
+
+               // ============================================================================================================
+               // INITIALIZATION
 
-               // the default mode for all charts
-               this.setMode('auto');
+               init();
        }
 
        // get or create a chart state, given a DOM element
                                y: {
                                        pixelsPerLabel: 15,
                                        valueFormatter: function (x) {
+                                               // we format legends with the state object
+                                               // no need to do anything here
                                                // return (Math.round(x*100) / 100).toLocaleString();
-                                               //return state.legendFormatValue(x);
-                                               //FIXME
+                                               // return state.legendFormatValue(x);
                                                return x;
                                        }
                                }
        NETDATA.morrisChartCreate = function(state, data) {
 
                state.morris_options = {
-                               element: state.element_chart_id,
+                               element: state.element_chart.id,
                                data: data.result.data,
                                xkey: 'time',
                                ykeys: data.dimension_names,