]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
Merge pull request #1070 from tperalta82/master
[netdata.git] / web / dashboard.js
1 // You can set the following variables before loading this script:
2 //
3 // var netdataNoDygraphs = true;        // do not use dygraph
4 // var netdataNoSparklines = true;      // do not use sparkline
5 // var netdataNoPeitys = true;          // do not use peity
6 // var netdataNoGoogleCharts = true;    // do not use google
7 // var netdataNoMorris = true;          // do not use morris
8 // var netdataNoEasyPieChart = true;    // do not use easy pie chart
9 // var netdataNoGauge = true;           // do not use gauge.js
10 // var netdataNoD3 = true;              // do not use D3
11 // var netdataNoC3 = true;              // do not use C3
12 // var netdataNoBootstrap = true;       // do not load bootstrap
13 // var netdataDontStart = true;         // do not start the thread to process the charts
14 // var netdataErrorCallback = null;     // Callback function that will be invoked upon error
15 // var netdataRegistry = true;          // Update the registry (default disabled)
16 // var netdataRegistryCallback = null;  // Callback function that will be invoked with one param,
17 //                                         the URLs from the registry
18 // var netdataShowHelp = false;         // enable/disable help (default enabled)
19 // var netdataShowAlarms = true;        // enable/disable alarms checks and notifications (default disabled)
20 //
21 // var netdataRegistryAfterMs = 1500    // the time to consult to registry on startup
22 //
23 // var netdataCallback = null;          // a function to call when netdata is ready
24 //                                      // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null;      // a callback to be called before netdata does anything else
26 //
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
31
32 //(function(window, document, undefined) {
33
34     // ------------------------------------------------------------------------
35     // compatibility fixes
36
37     // fix IE issue with console
38     if(!window.console) { window.console = { log: function(){} }; }
39
40     // if string.endsWith is not defined, define it
41     if(typeof String.prototype.endsWith !== 'function') {
42         String.prototype.endsWith = function(s) {
43             if(s.length > this.length) return false;
44             return this.slice(-s.length) === s;
45         };
46     }
47
48     // if string.startsWith is not defined, define it
49     if(typeof String.prototype.startsWith !== 'function') {
50         String.prototype.startsWith = function(s) {
51             if(s.length > this.length) return false;
52             return this.slice(s.length) === s;
53         };
54     }
55
56     // global namespace
57     var NETDATA = window.NETDATA || {};
58
59     NETDATA.name2id = function(s) {
60         return s
61             .replace(/ /g, '_')
62             .replace(/\(/g, '_')
63             .replace(/\)/g, '_')
64             .replace(/\./g, '_')
65             .replace(/\//g, '_');
66     };
67
68     // ----------------------------------------------------------------------------------------------------------------
69     // Detect the netdata server
70
71     // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72     // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73     NETDATA._scriptSource = function() {
74         var script = null;
75
76         if(typeof document.currentScript !== 'undefined') {
77             script = document.currentScript;
78         }
79         else {
80             var all_scripts = document.getElementsByTagName('script');
81             script = all_scripts[all_scripts.length - 1];
82         }
83
84         if (typeof script.getAttribute.length !== 'undefined')
85             script = script.src;
86         else
87             script = script.getAttribute('src', -1);
88
89         return script;
90     };
91
92     if(typeof netdataServer !== 'undefined')
93         NETDATA.serverDefault = netdataServer;
94     else {
95         var s = NETDATA._scriptSource();
96         if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97         else {
98             console.log('WARNING: Cannot detect the URL of the netdata server.');
99             NETDATA.serverDefault = null;
100         }
101     }
102
103     if(NETDATA.serverDefault === null)
104         NETDATA.serverDefault = '';
105     else if(NETDATA.serverDefault.slice(-1) !== '/')
106         NETDATA.serverDefault += '/';
107
108     // default URLs for all the external files we need
109     // make them RELATIVE so that the whole thing can also be
110     // installed under a web server
111     NETDATA.jQuery              = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112     NETDATA.peity_js            = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113     NETDATA.sparkline_js        = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114     NETDATA.easypiechart_js     = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115     NETDATA.gauge_js            = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116     NETDATA.dygraph_js          = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117     NETDATA.dygraph_smooth_js   = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118     NETDATA.raphael_js          = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119     NETDATA.c3_js               = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120     NETDATA.c3_css              = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121     NETDATA.d3_js               = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122     NETDATA.morris_js           = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123     NETDATA.morris_css          = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124     NETDATA.google_js           = 'https://www.google.com/jsapi';
125
126     NETDATA.themes = {
127         white: {
128             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.min.css',
129             dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161002-1',
130             background: '#FFFFFF',
131             foreground: '#000000',
132             grid: '#DDDDDD',
133             axis: '#CCCCCC',
134             colors: [   '#3366CC', '#DC3912',   '#109618', '#FF9900',   '#990099', '#DD4477',
135                         '#3B3EAC', '#66AA00',   '#0099C6', '#B82E2E',   '#AAAA11', '#5574A6',
136                         '#994499', '#22AA99',   '#6633CC', '#E67300',   '#316395', '#8B0707',
137                         '#329262', '#3B3EAC' ],
138             easypiechart_track: '#f0f0f0',
139             easypiechart_scale: '#dfe0e0',
140             gauge_pointer: '#C0C0C0',
141             gauge_stroke: '#F0F0F0',
142             gauge_gradient: false
143         },
144         slate: {
145             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css?v20161002-1',
146             dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161002-1',
147             background: '#272b30',
148             foreground: '#C8C8C8',
149             grid: '#35393e',
150             axis: '#35393e',
151 /*          colors: [   '#55bb33', '#ff2222',   '#0099C6', '#faa11b',   '#adbce0', '#DDDD00',
152                         '#4178ba', '#f58122',   '#a5cc39', '#f58667',   '#f5ef89', '#cf93c0',
153                         '#a5d18a', '#b8539d',   '#3954a3', '#c8a9cf',   '#c7de8a', '#fad20a',
154                         '#a6a479', '#a66da8' ],
155 */
156             colors: [   '#66AA00', '#FE3912',   '#3366CC', '#D66300',   '#0099C6', '#DDDD00',
157                         '#5054e6', '#EE9911',   '#BB44CC', '#e45757',   '#ef0aef', '#CC7700',
158                         '#22AA99', '#109618',   '#905bfd', '#f54882',   '#4381bf', '#ff3737',
159                         '#329262', '#3B3EFF' ],
160             easypiechart_track: '#373b40',
161             easypiechart_scale: '#373b40',
162             gauge_pointer: '#474b50',
163             gauge_stroke: '#373b40',
164             gauge_gradient: false
165         }
166     };
167
168     if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169         NETDATA.themes.current = NETDATA.themes[netdataTheme];
170     else
171         NETDATA.themes.current = NETDATA.themes.white;
172
173     NETDATA.colors = NETDATA.themes.current.colors;
174
175     // these are the colors Google Charts are using
176     // we have them here to attempt emulate their look and feel on the other chart libraries
177     // http://there4.io/2012/05/02/google-chart-color-list/
178     //NETDATA.colors        = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179     //                      '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180     //                      '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181
182     // an alternative set
183     // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184     //                         (blue)     (red)      (orange)   (green)    (pink)     (brown)    (purple)   (yellow)   (gray)
185     //NETDATA.colors        = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186
187     if(typeof netdataShowHelp === 'undefined')
188         netdataShowHelp = true;
189
190     if(typeof netdataShowAlarms === 'undefined')
191         netdataShowAlarms = false;
192
193     if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194         netdataRegistryAfterMs = 1500;
195
196     if(typeof netdataRegistry === 'undefined') {
197         // backward compatibility
198         if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199             netdataRegistry = true;
200         else
201             netdataRegistry = false;
202     }
203     if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204         netdataRegistry = true;
205
206     // ----------------------------------------------------------------------------------------------------------------
207     // the defaults for all charts
208
209     // if the user does not specify any of these, the following will be used
210
211     NETDATA.chartDefaults = {
212         host: NETDATA.serverDefault,    // the server to get data from
213         width: '100%',                  // the chart width - can be null
214         height: '100%',                 // the chart height - can be null
215         min_width: null,                // the chart minimum width - can be null
216         library: 'dygraph',             // the graphing library to use
217         method: 'average',              // the grouping method
218         before: 0,                      // panning
219         after: -600,                    // panning
220         pixels_per_point: 1,            // the detail of the chart
221         fill_luminance: 0.8             // luminance of colors in solit areas
222     };
223
224     // ----------------------------------------------------------------------------------------------------------------
225     // global options
226
227     NETDATA.options = {
228         pauseCallback: null,            // a callback when we are really paused
229
230         pause: false,                   // when enabled we don't auto-refresh the charts
231
232         targets: null,                  // an array of all the state objects that are
233                                         // currently active (independently of their
234                                         // viewport visibility)
235
236         updated_dom: true,              // when true, the DOM has been updated with
237                                         // new elements we have to check.
238
239         auto_refresher_fast_weight: 0,  // this is the current time in ms, spent
240                                         // rendering charts continiously.
241                                         // used with .current.fast_render_timeframe
242
243         page_is_visible: true,          // when true, this page is visible
244
245         auto_refresher_stop_until: 0,   // timestamp in ms - used internaly, to stop the
246                                         // auto-refresher for some time (when a chart is
247                                         // performing pan or zoom, we need to stop refreshing
248                                         // all other charts, to have the maximum speed for
249                                         // rendering the chart that is panned or zoomed).
250                                         // Used with .current.global_pan_sync_time
251
252         last_resized: new Date().getTime(), // the timestamp of the last resize request
253
254         last_page_scroll: 0,            // the timestamp the last time the page was scrolled
255
256         // the current profile
257         // we may have many...
258         current: {
259             pixels_per_point: 1,        // the minimum pixels per point for all charts
260                                         // increase this to speed javascript up
261                                         // each chart library has its own limit too
262                                         // the max of this and the chart library is used
263                                         // the final is calculated every time, so a change
264                                         // here will have immediate effect on the next chart
265                                         // update
266
267             idle_between_charts: 100,   // ms - how much time to wait between chart updates
268
269             fast_render_timeframe: 200, // ms - render continously until this time of continious
270                                         // rendering has been reached
271                                         // this setting is used to make it render e.g. 10
272                                         // charts at once, sleep idle_between_charts time
273                                         // and continue for another 10 charts.
274
275             idle_between_loops: 500,    // ms - if all charts have been updated, wait this
276                                         // time before starting again.
277
278             idle_parallel_loops: 100,   // ms - the time between parallel refresher updates
279
280             idle_lost_focus: 500,       // ms - when the window does not have focus, check
281                                         // if focus has been regained, every this time
282
283             global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284                                         // autorefreshing of charts is paused for this amount
285                                         // of time
286
287             sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288                                         // of time before setting up synchronized selections
289                                         // on hover.
290
291             sync_selection: true,       // enable or disable selection sync
292
293             pan_and_zoom_delay: 50,     // when panning or zooming, how ofter to update the chart
294
295             sync_pan_and_zoom: true,    // enable or disable pan and zoom sync
296
297             pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298
299             update_only_visible: true,  // enable or disable visibility management
300
301             parallel_refresher: true,   // enable parallel refresh of charts
302
303             concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304
305             destroy_on_hide: false,     // destroy charts when they are not visible
306
307             show_help: netdataShowHelp, // when enabled the charts will show some help
308             show_help_delay_show_ms: 500,
309             show_help_delay_hide_ms: 0,
310
311             eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312
313             stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314             stop_updates_while_resizing: 1000,  // ms - time to stop auto-refreshes while resizing the charts
315
316             double_click_speed: 500,    // ms - time between clicks / taps to detect double click/tap
317
318             smooth_plot: true,          // enable smooth plot, where possible
319
320             charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321
322             color_fill_opacity_line: 1.0,
323             color_fill_opacity_area: 0.2,
324             color_fill_opacity_stacked: 0.8,
325
326             pan_and_zoom_factor: 0.25,      // the increment when panning and zooming with the toolbox
327             pan_and_zoom_factor_multiplier_control: 2.0,
328             pan_and_zoom_factor_multiplier_shift: 3.0,
329             pan_and_zoom_factor_multiplier_alt: 4.0,
330
331             abort_ajax_on_scroll: false,            // kill pending ajax page scroll
332             async_on_scroll: false,                 // sync/async onscroll handler
333             onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334
335             setOptionCallback: function() { ; }
336         },
337
338         debug: {
339             show_boxes:         false,
340             main_loop:          false,
341             focus:              false,
342             visibility:         false,
343             chart_data_url:     false,
344             chart_errors:       false, // FIXME
345             chart_timing:       false,
346             chart_calls:        false,
347             libraries:          false,
348             dygraph:            false
349         }
350     };
351
352     NETDATA.statistics = {
353         refreshes_total: 0,
354         refreshes_active: 0,
355         refreshes_active_max: 0
356     };
357
358
359     // ----------------------------------------------------------------------------------------------------------------
360     // local storage options
361
362     NETDATA.localStorage = {
363         default: {},
364         current: {},
365         callback: {} // only used for resetting back to defaults
366     };
367
368     NETDATA.localStorageGet = function(key, def, callback) {
369         var ret = def;
370
371         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372             NETDATA.localStorage.default[key.toString()] = def;
373             NETDATA.localStorage.callback[key.toString()] = callback;
374         }
375
376         if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
377             try {
378                 // console.log('localStorage: loading "' + key.toString() + '"');
379                 ret = localStorage.getItem(key.toString());
380                 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
381                 if(ret === null || ret === 'undefined') {
382                     // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
383                     localStorage.setItem(key.toString(), JSON.stringify(def));
384                     ret = def;
385                 }
386                 else {
387                     // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
388                     ret = JSON.parse(ret);
389                     // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
390                 }
391             }
392             catch(error) {
393                 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
394                 ret = def;
395             }
396         }
397
398         if(typeof ret === 'undefined' || ret === 'undefined') {
399             console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
400             ret = def;
401         }
402
403         NETDATA.localStorage.current[key.toString()] = ret;
404         return ret;
405     };
406
407     NETDATA.localStorageSet = function(key, value, callback) {
408         if(typeof value === 'undefined' || value === 'undefined') {
409             console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
410         }
411
412         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
413             NETDATA.localStorage.default[key.toString()] = value;
414             NETDATA.localStorage.current[key.toString()] = value;
415             NETDATA.localStorage.callback[key.toString()] = callback;
416         }
417
418         if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
419             // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
420             try {
421                 localStorage.setItem(key.toString(), JSON.stringify(value));
422             }
423             catch(e) {
424                 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
425             }
426         }
427
428         NETDATA.localStorage.current[key.toString()] = value;
429         return value;
430     };
431
432     NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
433         for(var i in obj) {
434             if(typeof obj[i] === 'object') {
435                 //console.log('object ' + prefix + '.' + i.toString());
436                 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
437                 continue;
438             }
439
440             obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
441         }
442     };
443
444     NETDATA.setOption = function(key, value) {
445         if(key.toString() === 'setOptionCallback') {
446             if(typeof NETDATA.options.current.setOptionCallback === 'function') {
447                 NETDATA.options.current[key.toString()] = value;
448                 NETDATA.options.current.setOptionCallback();
449             }
450         }
451         else if(NETDATA.options.current[key.toString()] !== value) {
452             var name = 'options.' + key.toString();
453
454             if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
455                 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
456
457             //console.log(NETDATA.localStorage);
458             //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
459             //console.log(NETDATA.options);
460             NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
461
462             if(typeof NETDATA.options.current.setOptionCallback === 'function')
463                 NETDATA.options.current.setOptionCallback();
464         }
465
466         return true;
467     };
468
469     NETDATA.getOption = function(key) {
470         return NETDATA.options.current[key.toString()];
471     };
472
473     // read settings from local storage
474     NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
475
476     // always start with this option enabled.
477     NETDATA.setOption('stop_updates_when_focus_is_lost', true);
478
479     NETDATA.resetOptions = function() {
480         for(var i in NETDATA.localStorage.default) {
481             var a = i.split('.');
482
483             if(a[0] === 'options') {
484                 if(a[1] === 'setOptionCallback') continue;
485                 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
486                 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
487
488                 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
489             }
490             else if(a[0] === 'chart_heights') {
491                 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
492                     NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
493                 }
494             }
495         }
496     }
497
498     // ----------------------------------------------------------------------------------------------------------------
499
500     if(NETDATA.options.debug.main_loop === true)
501         console.log('welcome to NETDATA');
502
503     NETDATA.onresize = function() {
504         NETDATA.options.last_resized = new Date().getTime();
505         NETDATA.onscroll();
506     };
507
508     NETDATA.onscroll_updater_count = 0;
509     NETDATA.onscroll_updater_running = false;
510     NETDATA.onscroll_updater_last_run = 0;
511     NETDATA.onscroll_updater_watchdog = null;
512     NETDATA.onscroll_updater_max_duration = 0;
513     NETDATA.onscroll_updater_above_threshold_count = 0;
514     NETDATA.onscroll_updater = function() {
515         NETDATA.onscroll_updater_running = true;
516         NETDATA.onscroll_updater_count++;
517         var start = new Date().getTime();
518
519         var targets = NETDATA.options.targets;
520         var len = targets.length;
521
522         // when the user scrolls he sees that we have
523         // hidden all the not-visible charts
524         // using this little function we try to switch
525         // the charts back to visible quickly
526
527
528         if(NETDATA.options.abort_ajax_on_scroll === true) {
529             // we have to cancel pending requests too
530
531             while (len--) {
532                 if (targets[len]._updating === true) {
533                     if (typeof targets[len].xhr !== 'undefined') {
534                         targets[len].xhr.abort();
535                         targets[len].running = false;
536                         targets[len]._updating = false;
537                     }
538                     targets[len].isVisible();
539                 }
540             }
541         }
542         else {
543             // just find which chart is visible
544
545             while (len--)
546                 targets[len].isVisible();
547         }
548
549         var end = new Date().getTime();
550         // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
551
552         if(NETDATA.options.current.async_on_scroll === false) {
553             var dt = end - start;
554             if(dt > NETDATA.onscroll_updater_max_duration) {
555                 // console.log('max onscroll event handler duration increased to ' + dt);
556                 NETDATA.onscroll_updater_max_duration = dt;
557             }
558
559             if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
560                 // console.log('slow: ' + dt);
561                 NETDATA.onscroll_updater_above_threshold_count++;
562
563                 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
564                     NETDATA.setOption('async_on_scroll', true);
565                     console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
566                 }
567             }
568         }
569
570         NETDATA.onscroll_updater_last_run = start;
571         NETDATA.onscroll_updater_running = false;
572     };
573
574     NETDATA.onscroll = function() {
575         // console.log('onscroll');
576
577         NETDATA.options.last_page_scroll = new Date().getTime();
578         NETDATA.options.auto_refresher_stop_until = 0;
579
580         if(NETDATA.options.targets === null) return;
581
582         if(NETDATA.options.current.async_on_scroll === true) {
583             // async
584             if(NETDATA.onscroll_updater_running === false) {
585                 NETDATA.onscroll_updater_running = true;
586                 setTimeout(NETDATA.onscroll_updater, 0);
587             }
588             else {
589                 if(NETDATA.onscroll_updater_watchdog !== null)
590                     clearTimeout(NETDATA.onscroll_updater_watchdog);
591
592                 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
593                     if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
594                         // console.log('watchdog');
595                         NETDATA.onscroll_updater();
596                     }
597
598                     NETDATA.onscroll_updater_watchdog = null;
599                 }, 200);
600             }
601         }
602         else {
603             // sync
604             NETDATA.onscroll_updater();
605         }
606     };
607
608     window.onresize = NETDATA.onresize;
609     window.onscroll = NETDATA.onscroll;
610
611     // ----------------------------------------------------------------------------------------------------------------
612     // Error Handling
613
614     NETDATA.errorCodes = {
615         100: { message: "Cannot load chart library", alert: true },
616         101: { message: "Cannot load jQuery", alert: true },
617         402: { message: "Chart library not found", alert: false },
618         403: { message: "Chart library not enabled/is failed", alert: false },
619         404: { message: "Chart not found", alert: false },
620         405: { message: "Cannot download charts index from server", alert: true },
621         406: { message: "Invalid charts index downloaded from server", alert: true },
622         407: { message: "Cannot HELLO netdata server", alert: false },
623         408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
624         409: { message: "Cannot ACCESS netdata registry", alert: false },
625         410: { message: "Netdata registry ACCESS failed", alert: false },
626         411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
627         412: { message: "Netdata registry DELETE failed", alert: false },
628         413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
629         414: { message: "Netdata registry SWITCH failed", alert: false },
630         415: { message: "Netdata alarms download failed", alert: false },
631         416: { message: "Netdata alarms log download failed", alert: false },
632         417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
633         418: { message: "Netdata registry SEARCH failed", alert: false }
634     };
635     NETDATA.errorLast = {
636         code: 0,
637         message: "",
638         datetime: 0
639     };
640
641     NETDATA.error = function(code, msg) {
642         NETDATA.errorLast.code = code;
643         NETDATA.errorLast.message = msg;
644         NETDATA.errorLast.datetime = new Date().getTime();
645
646         console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
647
648         var ret = true;
649         if(typeof netdataErrorCallback === 'function') {
650            ret = netdataErrorCallback('system', code, msg);
651         }
652
653         if(ret && NETDATA.errorCodes[code].alert)
654             alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
655     };
656
657     NETDATA.errorReset = function() {
658         NETDATA.errorLast.code = 0;
659         NETDATA.errorLast.message = "You are doing fine!";
660         NETDATA.errorLast.datetime = 0;
661     };
662
663     // ----------------------------------------------------------------------------------------------------------------
664     // Chart Registry
665
666     // When multiple charts need the same chart, we avoid downloading it
667     // multiple times (and having it in browser memory multiple time)
668     // by using this registry.
669
670     // Every time we download a chart definition, we save it here with .add()
671     // Then we try to get it back with .get(). If that fails, we download it.
672
673     NETDATA.chartRegistry = {
674         charts: {},
675
676         fixid: function(id) {
677             return id.replace(/:/g, "_").replace(/\//g, "_");
678         },
679
680         add: function(host, id, data) {
681             host = this.fixid(host);
682             id   = this.fixid(id);
683
684             if(typeof this.charts[host] === 'undefined')
685                 this.charts[host] = {};
686
687             //console.log('added ' + host + '/' + id);
688             this.charts[host][id] = data;
689         },
690
691         get: function(host, id) {
692             host = this.fixid(host);
693             id   = this.fixid(id);
694
695             if(typeof this.charts[host] === 'undefined')
696                 return null;
697
698             if(typeof this.charts[host][id] === 'undefined')
699                 return null;
700
701             //console.log('cached ' + host + '/' + id);
702             return this.charts[host][id];
703         },
704
705         downloadAll: function(host, callback) {
706             while(host.slice(-1) === '/')
707                 host = host.substring(0, host.length - 1);
708
709             var self = this;
710
711             $.ajax({
712                 url: host + '/api/v1/charts',
713                 async: true,
714                 cache: false,
715                 xhrFields: { withCredentials: true } // required for the cookie
716             })
717             .done(function(data) {
718                 if(data !== null) {
719                     var h = NETDATA.chartRegistry.fixid(host);
720                     self.charts[h] = data.charts;
721                 }
722                 else NETDATA.error(406, host + '/api/v1/charts');
723
724                 if(typeof callback === 'function')
725                     callback(data);
726             })
727             .fail(function() {
728                 NETDATA.error(405, host + '/api/v1/charts');
729
730                 if(typeof callback === 'function')
731                     callback(null);
732             });
733         }
734     };
735
736     // ----------------------------------------------------------------------------------------------------------------
737     // Global Pan and Zoom on charts
738
739     // Using this structure are synchronize all the charts, so that
740     // when you pan or zoom one, all others are automatically refreshed
741     // to the same timespan.
742
743     NETDATA.globalPanAndZoom = {
744         seq: 0,                 // timestamp ms
745                                 // every time a chart is panned or zoomed
746                                 // we set the timestamp here
747                                 // then we use it as a sequence number
748                                 // to find if other charts are syncronized
749                                 // to this timerange
750
751         master: null,           // the master chart (state), to which all others
752                                 // are synchronized
753
754         force_before_ms: null,  // the timespan to sync all other charts
755         force_after_ms: null,
756
757         callback: null,
758
759         // set a new master
760         setMaster: function(state, after, before) {
761             if(NETDATA.options.current.sync_pan_and_zoom === false)
762                 return;
763
764             if(this.master !== null && this.master !== state)
765                 this.master.resetChart(true, true);
766
767             var now = new Date().getTime();
768             this.master = state;
769             this.seq = now;
770             this.force_after_ms = after;
771             this.force_before_ms = before;
772             NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
773
774             if(typeof this.callback === 'function')
775                 this.callback(true, after, before);
776         },
777
778         // clear the master
779         clearMaster: function() {
780             if(this.master !== null) {
781                 var st = this.master;
782                 this.master = null;
783                 st.resetChart();
784             }
785
786             this.master = null;
787             this.seq = 0;
788             this.force_after_ms = null;
789             this.force_before_ms = null;
790             NETDATA.options.auto_refresher_stop_until = 0;
791
792             if(typeof this.callback === 'function')
793                 this.callback(false, 0, 0);
794         },
795
796         // is the given state the master of the global
797         // pan and zoom sync?
798         isMaster: function(state) {
799             if(this.master === state) return true;
800             return false;
801         },
802
803         // are we currently have a global pan and zoom sync?
804         isActive: function() {
805             if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
806             return false;
807         },
808
809         // check if a chart, other than the master
810         // needs to be refreshed, due to the global pan and zoom
811         shouldBeAutoRefreshed: function(state) {
812             if(this.master === null || this.seq === 0)
813                 return false;
814
815             //if(state.needsRecreation())
816             //  return true;
817
818             if(state.tm.pan_and_zoom_seq === this.seq)
819                 return false;
820
821             return true;
822         }
823     };
824
825     // ----------------------------------------------------------------------------------------------------------------
826     // dimensions selection
827
828     // FIXME
829     // move color assignment to dimensions, here
830
831     dimensionStatus = function(parent, label, name_div, value_div, color) {
832         this.enabled = false;
833         this.parent = parent;
834         this.label = label;
835         this.name_div = null;
836         this.value_div = null;
837         this.color = NETDATA.themes.current.foreground;
838
839         if(parent.unselected_count === 0)
840             this.selected = true;
841         else
842             this.selected = false;
843
844         this.setOptions(name_div, value_div, color);
845     };
846
847     dimensionStatus.prototype.invalidate = function() {
848         this.name_div = null;
849         this.value_div = null;
850         this.enabled = false;
851     };
852
853     dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
854         this.color = color;
855
856         if(this.name_div != name_div) {
857             this.name_div = name_div;
858             this.name_div.title = this.label;
859             this.name_div.style.color = this.color;
860             if(this.selected === false)
861                 this.name_div.className = 'netdata-legend-name not-selected';
862             else
863                 this.name_div.className = 'netdata-legend-name selected';
864         }
865
866         if(this.value_div != value_div) {
867             this.value_div = value_div;
868             this.value_div.title = this.label;
869             this.value_div.style.color = this.color;
870             if(this.selected === false)
871                 this.value_div.className = 'netdata-legend-value not-selected';
872             else
873                 this.value_div.className = 'netdata-legend-value selected';
874         }
875
876         this.enabled = true;
877         this.setHandler();
878     };
879
880     dimensionStatus.prototype.setHandler = function() {
881         if(this.enabled === false) return;
882
883         var ds = this;
884
885         // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
886         this.name_div.onclick = this.value_div.onclick = function(e) {
887             e.preventDefault();
888             if(ds.isSelected()) {
889                 // this is selected
890                 if(e.shiftKey === true || e.ctrlKey === true) {
891                     // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
892                     ds.unselect();
893
894                     if(ds.parent.countSelected() === 0)
895                         ds.parent.selectAll();
896                 }
897                 else {
898                     // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
899                     if(ds.parent.countSelected() === 1) {
900                         ds.parent.selectAll();
901                     }
902                     else {
903                         ds.parent.selectNone();
904                         ds.select();
905                     }
906                 }
907             }
908             else {
909                 // this is not selected
910                 if(e.shiftKey === true || e.ctrlKey === true) {
911                     // control or shift key is pressed -> select this too
912                     ds.select();
913                 }
914                 else {
915                     // no key is pressed -> select only this
916                     ds.parent.selectNone();
917                     ds.select();
918                 }
919             }
920
921             ds.parent.state.redrawChart();
922         }
923     };
924
925     dimensionStatus.prototype.select = function() {
926         if(this.enabled === false) return;
927
928         this.name_div.className = 'netdata-legend-name selected';
929         this.value_div.className = 'netdata-legend-value selected';
930         this.selected = true;
931     };
932
933     dimensionStatus.prototype.unselect = function() {
934         if(this.enabled === false) return;
935
936         this.name_div.className = 'netdata-legend-name not-selected';
937         this.value_div.className = 'netdata-legend-value hidden';
938         this.selected = false;
939     };
940
941     dimensionStatus.prototype.isSelected = function() {
942         return(this.enabled === true && this.selected === true);
943     };
944
945     // ----------------------------------------------------------------------------------------------------------------
946
947     dimensionsVisibility = function(state) {
948         this.state = state;
949         this.len = 0;
950         this.dimensions = {};
951         this.selected_count = 0;
952         this.unselected_count = 0;
953     };
954
955     dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
956         if(typeof this.dimensions[label] === 'undefined') {
957             this.len++;
958             this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
959         }
960         else
961             this.dimensions[label].setOptions(name_div, value_div, color);
962
963         return this.dimensions[label];
964     };
965
966     dimensionsVisibility.prototype.dimensionGet = function(label) {
967         return this.dimensions[label];
968     };
969
970     dimensionsVisibility.prototype.invalidateAll = function() {
971         for(var d in this.dimensions)
972             this.dimensions[d].invalidate();
973     };
974
975     dimensionsVisibility.prototype.selectAll = function() {
976         for(var d in this.dimensions)
977             this.dimensions[d].select();
978     };
979
980     dimensionsVisibility.prototype.countSelected = function() {
981         var i = 0;
982         for(var d in this.dimensions)
983             if(this.dimensions[d].isSelected()) i++;
984
985         return i;
986     };
987
988     dimensionsVisibility.prototype.selectNone = function() {
989         for(var d in this.dimensions)
990             this.dimensions[d].unselect();
991     };
992
993     dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
994         var ret = new Array();
995         this.selected_count = 0;
996         this.unselected_count = 0;
997
998         var len = array.length;
999         while(len--) {
1000             var ds = this.dimensions[array[len]];
1001             if(typeof ds === 'undefined') {
1002                 // console.log(array[i] + ' is not found');
1003                 ret.unshift(false);
1004             }
1005             else if(ds.isSelected()) {
1006                 ret.unshift(true);
1007                 this.selected_count++;
1008             }
1009             else {
1010                 ret.unshift(false);
1011                 this.unselected_count++;
1012             }
1013         }
1014
1015         if(this.selected_count === 0 && this.unselected_count !== 0) {
1016             this.selectAll();
1017             return this.selected2BooleanArray(array);
1018         }
1019
1020         return ret;
1021     };
1022
1023
1024     // ----------------------------------------------------------------------------------------------------------------
1025     // global selection sync
1026
1027     NETDATA.globalSelectionSync = {
1028         state: null,
1029         dont_sync_before: 0,
1030         last_t: 0,
1031         slaves: [],
1032
1033         stop: function() {
1034             if(this.state !== null)
1035                 this.state.globalSelectionSyncStop();
1036         },
1037
1038         delay: function() {
1039             if(this.state !== null) {
1040                 this.state.globalSelectionSyncDelay();
1041             }
1042         }
1043     };
1044
1045     // ----------------------------------------------------------------------------------------------------------------
1046     // Our state object, where all per-chart values are stored
1047
1048     chartState = function(element) {
1049         var self = $(element);
1050         this.element = element;
1051
1052         // IMPORTANT:
1053         // all private functions should use 'that', instead of 'this'
1054         var that = this;
1055
1056         /* error() - private
1057          * show an error instead of the chart
1058          */
1059         var error = function(msg) {
1060             var ret = true;
1061
1062             if(typeof netdataErrorCallback === 'function') {
1063                 ret = netdataErrorCallback('chart', that.id, msg);
1064             }
1065
1066             if(ret) {
1067                 that.element.innerHTML = that.id + ': ' + msg;
1068                 that.enabled = false;
1069                 that.current = that.pan;
1070             }
1071         };
1072
1073         // GUID - a unique identifier for the chart
1074         this.uuid = NETDATA.guid();
1075
1076         // string - the name of chart
1077         this.id = self.data('netdata');
1078
1079         // string - the key for localStorage settings
1080         this.settings_id = self.data('id') || null;
1081
1082         // the user given dimensions of the element
1083         this.width = self.data('width') || NETDATA.chartDefaults.width;
1084         this.height = self.data('height') || NETDATA.chartDefaults.height;
1085
1086         if(this.settings_id !== null) {
1087             this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1088                 // this is the callback that will be called
1089                 // if and when the user resets all localStorage variables
1090                 // to their defaults
1091
1092                 resizeChartToHeight(height);
1093             });
1094         }
1095
1096         // string - the netdata server URL, without any path
1097         this.host = self.data('host') || NETDATA.chartDefaults.host;
1098
1099         // make sure the host does not end with /
1100         // all netdata API requests use absolute paths
1101         while(this.host.slice(-1) === '/')
1102             this.host = this.host.substring(0, this.host.length - 1);
1103
1104         // string - the grouping method requested by the user
1105         this.method = self.data('method') || NETDATA.chartDefaults.method;
1106
1107         // the time-range requested by the user
1108         this.after = self.data('after') || NETDATA.chartDefaults.after;
1109         this.before = self.data('before') || NETDATA.chartDefaults.before;
1110
1111         // the pixels per point requested by the user
1112         this.pixels_per_point = self.data('pixels-per-point') || 1;
1113         this.points = self.data('points') || null;
1114
1115         // the dimensions requested by the user
1116         this.dimensions = self.data('dimensions') || null;
1117
1118         // the chart library requested by the user
1119         this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1120
1121         // object - the chart library used
1122         this.library = null;
1123
1124         // color management
1125         this.colors = null;
1126         this.colors_assigned = {};
1127         this.colors_available = null;
1128
1129         // the element already created by the user
1130         this.element_message = null;
1131
1132         // the element with the chart
1133         this.element_chart = null;
1134
1135         // the element with the legend of the chart (if created by us)
1136         this.element_legend = null;
1137         this.element_legend_childs = {
1138             hidden: null,
1139             title_date: null,
1140             title_time: null,
1141             title_units: null,
1142             nano: null,
1143             nano_options: null,
1144             series: null
1145         };
1146
1147         this.chart_url = null;                      // string - the url to download chart info
1148         this.chart = null;                          // object - the chart as downloaded from the server
1149
1150         this.title = self.data('title') || null;    // the title of the chart
1151         this.units = self.data('units') || null;    // the units of the chart dimensions
1152         this.append_options = self.data('append-options') || null;  // the units of the chart dimensions
1153
1154         this.running = false;                       // boolean - true when the chart is being refreshed now
1155         this.validated = false;                     // boolean - has the chart been validated?
1156         this.enabled = true;                        // boolean - is the chart enabled for refresh?
1157         this.paused = false;                        // boolean - is the chart paused for any reason?
1158         this.selected = false;                      // boolean - is the chart shown a selection?
1159         this.debug = false;                         // boolean - console.log() debug info about this chart
1160
1161         this.netdata_first = 0;                     // milliseconds - the first timestamp in netdata
1162         this.netdata_last = 0;                      // milliseconds - the last timestamp in netdata
1163         this.requested_after = null;                // milliseconds - the timestamp of the request after param
1164         this.requested_before = null;               // milliseconds - the timestamp of the request before param
1165         this.requested_padding = null;
1166         this.view_after = 0;
1167         this.view_before = 0;
1168
1169         this.auto = {
1170             name: 'auto',
1171             autorefresh: true,
1172             force_update_at: 0, // the timestamp to force the update at
1173             force_before_ms: null,
1174             force_after_ms: null
1175         };
1176         this.pan = {
1177             name: 'pan',
1178             autorefresh: false,
1179             force_update_at: 0, // the timestamp to force the update at
1180             force_before_ms: null,
1181             force_after_ms: null
1182         };
1183         this.zoom = {
1184             name: 'zoom',
1185             autorefresh: false,
1186             force_update_at: 0, // the timestamp to force the update at
1187             force_before_ms: null,
1188             force_after_ms: null
1189         };
1190
1191         // this is a pointer to one of the sub-classes below
1192         // auto, pan, zoom
1193         this.current = this.auto;
1194
1195         // check the requested library is available
1196         // we don't initialize it here - it will be initialized when
1197         // this chart will be first used
1198         if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1199             NETDATA.error(402, that.library_name);
1200             error('chart library "' + that.library_name + '" is not found');
1201             return;
1202         }
1203         else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1204             NETDATA.error(403, that.library_name);
1205             error('chart library "' + that.library_name + '" is not enabled');
1206             return;
1207         }
1208         else
1209             that.library = NETDATA.chartLibraries[that.library_name];
1210
1211         // milliseconds - the time the last refresh took
1212         this.refresh_dt_ms = 0;
1213
1214         // if we need to report the rendering speed
1215         // find the element that needs to be updated
1216         var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1217
1218         if(refresh_dt_element_name !== null)
1219             this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1220         else
1221             this.refresh_dt_element = null;
1222
1223         this.dimensions_visibility = new dimensionsVisibility(this);
1224
1225         this._updating = false;
1226
1227         // ============================================================================================================
1228         // PRIVATE FUNCTIONS
1229
1230         var createDOM = function() {
1231             if(that.enabled === false) return;
1232
1233             if(that.element_message !== null) that.element_message.innerHTML = '';
1234             if(that.element_legend !== null) that.element_legend.innerHTML = '';
1235             if(that.element_chart !== null) that.element_chart.innerHTML = '';
1236
1237             that.element.innerHTML = '';
1238
1239             that.element_message = document.createElement('div');
1240             that.element_message.className = ' netdata-message hidden';
1241             that.element.appendChild(that.element_message);
1242
1243             that.element_chart = document.createElement('div');
1244             that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1245             that.element.appendChild(that.element_chart);
1246
1247             if(that.hasLegend() === true) {
1248                 that.element.className = "netdata-container-with-legend";
1249                 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1250
1251                 that.element_legend = document.createElement('div');
1252                 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1253                 that.element.appendChild(that.element_legend);
1254             }
1255             else {
1256                 that.element.className = "netdata-container";
1257                 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1258
1259                 that.element_legend = null;
1260             }
1261             that.element_legend_childs.series = null;
1262
1263             if(typeof(that.width) === 'string')
1264                 $(that.element).css('width', that.width);
1265             else if(typeof(that.width) === 'number')
1266                 $(that.element).css('width', that.width + 'px');
1267
1268             if(typeof(that.library.aspect_ratio) === 'undefined') {
1269                 if(typeof(that.height) === 'string')
1270                     $(that.element).css('height', that.height);
1271                 else if(typeof(that.height) === 'number')
1272                     $(that.element).css('height', that.height + 'px');
1273             }
1274             else {
1275                 var w = that.element.offsetWidth;
1276                 if(w === null || w === 0) {
1277                     // the div is hidden
1278                     // this will resize the chart when next viewed
1279                     that.tm.last_resized = 0;
1280                 }
1281                 else
1282                     $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1283             }
1284
1285             if(NETDATA.chartDefaults.min_width !== null)
1286                 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1287
1288             that.tm.last_dom_created = new Date().getTime();
1289
1290             showLoading();
1291         };
1292
1293         /* init() private
1294          * initialize state variables
1295          * destroy all (possibly) created state elements
1296          * create the basic DOM for a chart
1297          */
1298         var init = function() {
1299             if(that.enabled === false) return;
1300
1301             that.paused = false;
1302             that.selected = false;
1303
1304             that.chart_created = false;         // boolean - is the library.create() been called?
1305             that.updates_counter = 0;           // numeric - the number of refreshes made so far
1306             that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1307             that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1308
1309             that.tm = {
1310                 last_initialized: 0,        // milliseconds - the timestamp it was last initialized
1311                 last_dom_created: 0,        // milliseconds - the timestamp its DOM was last created
1312                 last_mode_switch: 0,        // milliseconds - the timestamp it switched modes
1313
1314                 last_info_downloaded: 0,    // milliseconds - the timestamp we downloaded the chart
1315                 last_updated: 0,            // the timestamp the chart last updated with data
1316                 pan_and_zoom_seq: 0,        // the sequence number of the global synchronization
1317                                             // between chart.
1318                                             // Used with NETDATA.globalPanAndZoom.seq
1319                 last_visible_check: 0,      // the time we last checked if it is visible
1320                 last_resized: 0,            // the time the chart was resized
1321                 last_hidden: 0,             // the time the chart was hidden
1322                 last_unhidden: 0,           // the time the chart was unhidden
1323                 last_autorefreshed: 0       // the time the chart was last refreshed
1324             };
1325
1326             that.data = null;               // the last data as downloaded from the netdata server
1327             that.data_url = 'invalid://';   // string - the last url used to update the chart
1328             that.data_points = 0;           // number - the number of points returned from netdata
1329             that.data_after = 0;            // milliseconds - the first timestamp of the data
1330             that.data_before = 0;           // milliseconds - the last timestamp of the data
1331             that.data_update_every = 0;     // milliseconds - the frequency to update the data
1332
1333             that.tm.last_initialized = new Date().getTime();
1334             createDOM();
1335
1336             that.setMode('auto');
1337         };
1338
1339         var maxMessageFontSize = function() {
1340             // normally we want a font size, as tall as the element
1341             var h = that.element_message.clientHeight;
1342
1343             // but give it some air, 20% let's say, or 5 pixels min
1344             var lost = Math.max(h * 0.2, 5);
1345             h -= lost;
1346
1347             // center the text, vertically
1348             var paddingTop = (lost - 5) / 2;
1349
1350             // but check the width too
1351             // it should fit 10 characters in it
1352             var w = that.element_message.clientWidth / 10;
1353             if(h > w) {
1354                 paddingTop += (h - w) / 2;
1355                 h = w;
1356             }
1357
1358             // and don't make it too huge
1359             // 5% of the screen size is good
1360             if(h > screen.height / 20) {
1361                 paddingTop += (h - (screen.height / 20)) / 2;
1362                 h = screen.height / 20;
1363             }
1364
1365             // set it
1366             that.element_message.style.fontSize = h.toString() + 'px';
1367             that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1368         };
1369
1370         var showMessage = function(msg) {
1371             that.element_message.className = 'netdata-message';
1372             that.element_message.innerHTML = msg;
1373             that.element_message.style.fontSize = 'x-small';
1374             that.element_message.style.paddingTop = '0px';
1375             that.___messageHidden___ = undefined;
1376         };
1377
1378         var showMessageIcon = function(icon) {
1379             that.element_message.innerHTML = icon;
1380             that.element_message.className = 'netdata-message icon';
1381             maxMessageFontSize();
1382             that.___messageHidden___ = undefined;
1383         };
1384
1385         var hideMessage = function() {
1386             if(typeof that.___messageHidden___ === 'undefined') {
1387                 that.___messageHidden___ = true;
1388                 that.element_message.className = 'netdata-message hidden';
1389             }
1390         };
1391
1392         var showRendering = function() {
1393             var icon;
1394             if(that.chart !== null) {
1395                 if(that.chart.chart_type === 'line')
1396                     icon = '<i class="fa fa-line-chart"></i>';
1397                 else
1398                     icon = '<i class="fa fa-area-chart"></i>';
1399             }
1400             else
1401                 icon = '<i class="fa fa-area-chart"></i>';
1402
1403             showMessageIcon(icon + ' netdata');
1404         };
1405
1406         var showLoading = function() {
1407             if(that.chart_created === false) {
1408                 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1409                 return true;
1410             }
1411             return false;
1412         };
1413
1414         var isHidden = function() {
1415             if(typeof that.___chartIsHidden___ !== 'undefined')
1416                 return true;
1417
1418             return false;
1419         };
1420
1421         // hide the chart, when it is not visible - called from isVisible()
1422         var hideChart = function() {
1423             // hide it, if it is not already hidden
1424             if(isHidden() === true) return;
1425
1426             if(that.chart_created === true) {
1427                 if(NETDATA.options.current.destroy_on_hide === true) {
1428                     // we should destroy it
1429                     init();
1430                 }
1431                 else {
1432                     showRendering();
1433                     that.element_chart.style.display = 'none';
1434                     if(that.element_legend !== null) that.element_legend.style.display = 'none';
1435                     that.tm.last_hidden = new Date().getTime();
1436
1437                     // de-allocate data
1438                     // This works, but I not sure there are no corner cases somewhere
1439                     // so it is commented - if the user has memory issues he can
1440                     // set Destroy on Hide for all charts
1441                     // that.data = null;
1442                 }
1443             }
1444
1445             that.___chartIsHidden___ = true;
1446         };
1447
1448         // unhide the chart, when it is visible - called from isVisible()
1449         var unhideChart = function() {
1450             if(isHidden() === false) return;
1451
1452             that.___chartIsHidden___ = undefined;
1453             that.updates_since_last_unhide = 0;
1454
1455             if(that.chart_created === false) {
1456                 // we need to re-initialize it, to show our background
1457                 // logo in bootstrap tabs, until the chart loads
1458                 init();
1459             }
1460             else {
1461                 that.tm.last_unhidden = new Date().getTime();
1462                 that.element_chart.style.display = '';
1463                 if(that.element_legend !== null) that.element_legend.style.display = '';
1464                 resizeChart();
1465                 hideMessage();
1466             }
1467         };
1468
1469         var canBeRendered = function() {
1470             if(isHidden() === true || that.isVisible(true) === false)
1471                 return false;
1472
1473             return true;
1474         };
1475
1476         // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1477         var callChartLibraryUpdateSafely = function(data) {
1478             var status;
1479
1480             if(canBeRendered() === false)
1481                 return false;
1482
1483             if(NETDATA.options.debug.chart_errors === true)
1484                 status = that.library.update(that, data);
1485             else {
1486                 try {
1487                     status = that.library.update(that, data);
1488                 }
1489                 catch(err) {
1490                     status = false;
1491                 }
1492             }
1493
1494             if(status === false) {
1495                 error('chart failed to be updated as ' + that.library_name);
1496                 return false;
1497             }
1498
1499             return true;
1500         };
1501
1502         // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1503         var callChartLibraryCreateSafely = function(data) {
1504             var status;
1505
1506             if(canBeRendered() === false)
1507                 return false;
1508
1509             if(NETDATA.options.debug.chart_errors === true)
1510                 status = that.library.create(that, data);
1511             else {
1512                 try {
1513                     status = that.library.create(that, data);
1514                 }
1515                 catch(err) {
1516                     status = false;
1517                 }
1518             }
1519
1520             if(status === false) {
1521                 error('chart failed to be created as ' + that.library_name);
1522                 return false;
1523             }
1524
1525             that.chart_created = true;
1526             that.updates_since_last_creation = 0;
1527             return true;
1528         };
1529
1530         // ----------------------------------------------------------------------------------------------------------------
1531         // Chart Resize
1532
1533         // resizeChart() - private
1534         // to be called just before the chart library to make sure that
1535         // a properly sized dom is available
1536         var resizeChart = function() {
1537             if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1538                 if(that.chart_created === false) return;
1539
1540                 if(that.needsRecreation()) {
1541                     init();
1542                 }
1543                 else if(typeof that.library.resize === 'function') {
1544                     that.library.resize(that);
1545
1546                     if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1547                         $(that.element_legend_childs.nano).nanoScroller();
1548
1549                     maxMessageFontSize();
1550                 }
1551
1552                 that.tm.last_resized = new Date().getTime();
1553             }
1554         };
1555
1556         // this is the actual chart resize algorithm
1557         // it will:
1558         // - resize the entire container
1559         // - update the internal states
1560         // - resize the chart as the div changes height
1561         // - update the scrollbar of the legend
1562         var resizeChartToHeight = function(h) {
1563             // console.log(h);
1564             that.element.style.height = h;
1565
1566             if(that.settings_id !== null)
1567                 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1568
1569             var now = new Date().getTime();
1570             NETDATA.options.last_page_scroll = now;
1571             NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1572
1573             // force a resize
1574             that.tm.last_resized = 0;
1575             resizeChart();
1576         };
1577
1578         this.resizeHandler = function(e) {
1579             e.preventDefault();
1580
1581             if(typeof this.event_resize === 'undefined'
1582                 || this.event_resize.chart_original_w === 'undefined'
1583                 || this.event_resize.chart_original_h === 'undefined')
1584                 this.event_resize = {
1585                     chart_original_w: this.element.clientWidth,
1586                     chart_original_h: this.element.clientHeight,
1587                     last: 0
1588                 };
1589
1590             if(e.type === 'touchstart') {
1591                 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1592                 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1593             }
1594             else {
1595                 this.event_resize.mouse_start_x = e.clientX;
1596                 this.event_resize.mouse_start_y = e.clientY;
1597             }
1598
1599             this.event_resize.chart_start_w = this.element.clientWidth;
1600             this.event_resize.chart_start_h = this.element.clientHeight;
1601             this.event_resize.chart_last_w = this.element.clientWidth;
1602             this.event_resize.chart_last_h = this.element.clientHeight;
1603
1604             var now = new Date().getTime();
1605             if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1606                 // double click / double tap event
1607
1608                 // the optimal height of the chart
1609                 // showing the entire legend
1610                 var optimal = this.event_resize.chart_last_h
1611                         + this.element_legend_childs.content.scrollHeight
1612                         - this.element_legend_childs.content.clientHeight;
1613
1614                 // if we are not optimal, be optimal
1615                 if(this.event_resize.chart_last_h != optimal)
1616                     resizeChartToHeight(optimal.toString() + 'px');
1617
1618                 // else if we do not have the original height
1619                 // reset to the original height
1620                 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1621                     resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1622             }
1623             else {
1624                 this.event_resize.last = now;
1625
1626                 // process movement event
1627                 document.onmousemove =
1628                 document.ontouchmove =
1629                 this.element_legend_childs.resize_handler.onmousemove =
1630                 this.element_legend_childs.resize_handler.ontouchmove =
1631                     function(e) {
1632                         var y = null;
1633
1634                         switch(e.type) {
1635                             case 'mousemove': y = e.clientY; break;
1636                             case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1637                         }
1638
1639                         if(y !== null) {
1640                             var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1641
1642                             if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1643                                 resizeChartToHeight(newH.toString() + 'px');
1644                                 that.event_resize.chart_last_h = newH;
1645                             }
1646                         }
1647                     };
1648
1649                 // process end event
1650                 document.onmouseup =
1651                 document.ontouchend =
1652                 this.element_legend_childs.resize_handler.onmouseup =
1653                 this.element_legend_childs.resize_handler.ontouchend =
1654                     function(e) {
1655                         // remove all the hooks
1656                         document.onmouseup =
1657                         document.onmousemove =
1658                         document.ontouchmove =
1659                         document.ontouchend =
1660                         that.element_legend_childs.resize_handler.onmousemove =
1661                         that.element_legend_childs.resize_handler.ontouchmove =
1662                         that.element_legend_childs.resize_handler.onmouseout =
1663                         that.element_legend_childs.resize_handler.onmouseup =
1664                         that.element_legend_childs.resize_handler.ontouchend =
1665                             null;
1666
1667                         // allow auto-refreshes
1668                         NETDATA.options.auto_refresher_stop_until = 0;
1669                     };
1670             }
1671         };
1672
1673
1674         var noDataToShow = function() {
1675             showMessageIcon('<i class="fa fa-warning"></i> empty');
1676             that.legendUpdateDOM();
1677             that.tm.last_autorefreshed = new Date().getTime();
1678             // that.data_update_every = 30 * 1000;
1679             //that.element_chart.style.display = 'none';
1680             //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1681             //that.___chartIsHidden___ = true;
1682         };
1683
1684         // ============================================================================================================
1685         // PUBLIC FUNCTIONS
1686
1687         this.error = function(msg) {
1688             error(msg);
1689         };
1690
1691         this.setMode = function(m) {
1692             if(this.current !== null && this.current.name === m) return;
1693
1694             if(m === 'auto')
1695                 this.current = this.auto;
1696             else if(m === 'pan')
1697                 this.current = this.pan;
1698             else if(m === 'zoom')
1699                 this.current = this.zoom;
1700             else
1701                 this.current = this.auto;
1702
1703             this.current.force_update_at = 0;
1704             this.current.force_before_ms = null;
1705             this.current.force_after_ms = null;
1706
1707             this.tm.last_mode_switch = new Date().getTime();
1708         };
1709
1710         // ----------------------------------------------------------------------------------------------------------------
1711         // global selection sync
1712
1713         // prevent to global selection sync for some time
1714         this.globalSelectionSyncDelay = function(ms) {
1715             if(NETDATA.options.current.sync_selection === false)
1716                 return;
1717
1718             if(typeof ms === 'number')
1719                 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1720             else
1721                 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1722         };
1723
1724         // can we globally apply selection sync?
1725         this.globalSelectionSyncAbility = function() {
1726             if(NETDATA.options.current.sync_selection === false)
1727                 return false;
1728
1729             if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1730                 return false;
1731
1732             return true;
1733         };
1734
1735         this.globalSelectionSyncIsMaster = function() {
1736             if(NETDATA.globalSelectionSync.state === this)
1737                 return true;
1738             else
1739                 return false;
1740         };
1741
1742         // this chart is the master of the global selection sync
1743         this.globalSelectionSyncBeMaster = function() {
1744             // am I the master?
1745             if(this.globalSelectionSyncIsMaster()) {
1746                 if(this.debug === true)
1747                     this.log('sync: I am the master already.');
1748
1749                 return;
1750             }
1751
1752             if(NETDATA.globalSelectionSync.state) {
1753                 if(this.debug === true)
1754                     this.log('sync: I am not the sync master. Resetting global sync.');
1755
1756                 this.globalSelectionSyncStop();
1757             }
1758
1759             // become the master
1760             if(this.debug === true)
1761                 this.log('sync: becoming sync master.');
1762
1763             this.selected = true;
1764             NETDATA.globalSelectionSync.state = this;
1765
1766             // find the all slaves
1767             var targets = NETDATA.options.targets;
1768             var len = targets.length;
1769             while(len--) {
1770                 st = targets[len];
1771
1772                 if(st === this) {
1773                     if(this.debug === true)
1774                         st.log('sync: not adding me to sync');
1775                 }
1776                 else if(st.globalSelectionSyncIsEligible()) {
1777                     if(this.debug === true)
1778                         st.log('sync: adding to sync as slave');
1779
1780                     st.globalSelectionSyncBeSlave();
1781                 }
1782             }
1783
1784             // this.globalSelectionSyncDelay(100);
1785         };
1786
1787         // can the chart participate to the global selection sync as a slave?
1788         this.globalSelectionSyncIsEligible = function() {
1789             if(this.enabled === true
1790                 && this.library !== null
1791                 && typeof this.library.setSelection === 'function'
1792                 && this.isVisible() === true
1793                 && this.chart_created === true)
1794                 return true;
1795
1796             return false;
1797         };
1798
1799         // this chart becomes a slave of the global selection sync
1800         this.globalSelectionSyncBeSlave = function() {
1801             if(NETDATA.globalSelectionSync.state !== this)
1802                 NETDATA.globalSelectionSync.slaves.push(this);
1803         };
1804
1805         // sync all the visible charts to the given time
1806         // this is to be called from the chart libraries
1807         this.globalSelectionSync = function(t) {
1808             if(this.globalSelectionSyncAbility() === false) {
1809                 if(this.debug === true)
1810                     this.log('sync: cannot sync (yet?).');
1811
1812                 return;
1813             }
1814
1815             if(this.globalSelectionSyncIsMaster() === false) {
1816                 if(this.debug === true)
1817                     this.log('sync: trying to be sync master.');
1818
1819                 this.globalSelectionSyncBeMaster();
1820
1821                 if(this.globalSelectionSyncAbility() === false) {
1822                     if(this.debug === true)
1823                         this.log('sync: cannot sync (yet?).');
1824
1825                     return;
1826                 }
1827             }
1828
1829             NETDATA.globalSelectionSync.last_t = t;
1830             $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1831                 st.setSelection(t);
1832             });
1833         };
1834
1835         // stop syncing all charts to the given time
1836         this.globalSelectionSyncStop = function() {
1837             if(NETDATA.globalSelectionSync.slaves.length) {
1838                 if(this.debug === true)
1839                     this.log('sync: cleaning up...');
1840
1841                 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1842                     if(st === that) {
1843                         if(that.debug === true)
1844                             st.log('sync: not adding me to sync stop');
1845                     }
1846                     else {
1847                         if(that.debug === true)
1848                             st.log('sync: removed slave from sync');
1849
1850                         st.clearSelection();
1851                     }
1852                 });
1853
1854                 NETDATA.globalSelectionSync.last_t = 0;
1855                 NETDATA.globalSelectionSync.slaves = [];
1856                 NETDATA.globalSelectionSync.state = null;
1857             }
1858
1859             this.clearSelection();
1860         };
1861
1862         this.setSelection = function(t) {
1863             if(typeof this.library.setSelection === 'function') {
1864                 if(this.library.setSelection(this, t) === true)
1865                     this.selected = true;
1866                 else
1867                     this.selected = false;
1868             }
1869             else this.selected = true;
1870
1871             if(this.selected === true && this.debug === true)
1872                 this.log('selection set to ' + t.toString());
1873
1874             return this.selected;
1875         };
1876
1877         this.clearSelection = function() {
1878             if(this.selected === true) {
1879                 if(typeof this.library.clearSelection === 'function') {
1880                     if(this.library.clearSelection(this) === true)
1881                         this.selected = false;
1882                     else
1883                         this.selected = true;
1884                 }
1885                 else this.selected = false;
1886
1887                 if(this.selected === false && this.debug === true)
1888                     this.log('selection cleared');
1889
1890                 this.legendReset();
1891             }
1892
1893             return this.selected;
1894         };
1895
1896         // find if a timestamp (ms) is shown in the current chart
1897         this.timeIsVisible = function(t) {
1898             if(t >= this.data_after && t <= this.data_before)
1899                 return true;
1900             return false;
1901         };
1902
1903         this.calculateRowForTime = function(t) {
1904             if(this.timeIsVisible(t) === false) return -1;
1905             return Math.floor((t - this.data_after) / this.data_update_every);
1906         };
1907
1908         // ----------------------------------------------------------------------------------------------------------------
1909
1910         // console logging
1911         this.log = function(msg) {
1912             console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1913         };
1914
1915         this.pauseChart = function() {
1916             if(this.paused === false) {
1917                 if(this.debug === true)
1918                     this.log('pauseChart()');
1919
1920                 this.paused = true;
1921             }
1922         };
1923
1924         this.unpauseChart = function() {
1925             if(this.paused === true) {
1926                 if(this.debug === true)
1927                     this.log('unpauseChart()');
1928
1929                 this.paused = false;
1930             }
1931         };
1932
1933         this.resetChart = function(dont_clear_master, dont_update) {
1934             if(this.debug === true)
1935                 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1936
1937             if(typeof dont_clear_master === 'undefined')
1938                 dont_clear_master = false;
1939
1940             if(typeof dont_update === 'undefined')
1941                 dont_update = false;
1942
1943             if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1944                 if(this.debug === true)
1945                     this.log('resetChart() diverting to clearMaster().');
1946                 // this will call us back with master === true
1947                 NETDATA.globalPanAndZoom.clearMaster();
1948                 return;
1949             }
1950
1951             this.clearSelection();
1952
1953             this.tm.pan_and_zoom_seq = 0;
1954
1955             this.setMode('auto');
1956             this.current.force_update_at = 0;
1957             this.current.force_before_ms = null;
1958             this.current.force_after_ms = null;
1959             this.tm.last_autorefreshed = 0;
1960             this.paused = false;
1961             this.selected = false;
1962             this.enabled = true;
1963             // this.debug = false;
1964
1965             // do not update the chart here
1966             // or the chart will flip-flop when it is the master
1967             // of a selection sync and another chart becomes
1968             // the new master
1969
1970             if(dont_update !== true && this.isVisible() === true) {
1971                 this.updateChart();
1972             }
1973         };
1974
1975         this.updateChartPanOrZoom = function(after, before) {
1976             var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1977             var ret = true;
1978
1979             if(this.debug === true)
1980                 this.log(logme);
1981
1982             if(before < after) {
1983                 if(this.debug === true)
1984                     this.log(logme + 'flipped parameters, rejecting it.');
1985
1986                 return false;
1987             }
1988
1989             if(typeof this.fixed_min_duration === 'undefined')
1990                 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1991
1992             var min_duration = this.fixed_min_duration;
1993             var current_duration = Math.round(this.view_before - this.view_after);
1994
1995             // round the numbers
1996             after = Math.round(after);
1997             before = Math.round(before);
1998
1999             // align them to update_every
2000             // stretching them further away
2001             after -= after % this.data_update_every;
2002             before += this.data_update_every - (before % this.data_update_every);
2003
2004             // the final wanted duration
2005             var wanted_duration = before - after;
2006
2007             // to allow panning, accept just a point below our minimum
2008             if((current_duration - this.data_update_every) < min_duration)
2009                 min_duration = current_duration - this.data_update_every;
2010
2011             // we do it, but we adjust to minimum size and return false
2012             // when the wanted size is below the current and the minimum
2013             // and we zoom
2014             if(wanted_duration < current_duration && wanted_duration < min_duration) {
2015                 if(this.debug === true)
2016                     this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2017
2018                 min_duration = this.fixed_min_duration;
2019
2020                 var dt = (min_duration - wanted_duration) / 2;
2021                 before += dt;
2022                 after -= dt;
2023                 wanted_duration = before - after;
2024                 ret = false;
2025             }
2026
2027             var tolerance = this.data_update_every * 2;
2028             var movement = Math.abs(before - this.view_before);
2029
2030             if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2031                 if(this.debug === true)
2032                     this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2033                 return false;
2034             }
2035
2036             if(this.current.name === 'auto') {
2037                 this.log(logme + 'caller called me with mode: ' + this.current.name);
2038                 this.setMode('pan');
2039             }
2040
2041             if(this.debug === true)
2042                 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2043
2044             this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
2045             this.current.force_after_ms = after;
2046             this.current.force_before_ms = before;
2047             NETDATA.globalPanAndZoom.setMaster(this, after, before);
2048             return ret;
2049         };
2050
2051         this.legendFormatValue = function(value) {
2052             if(value === null || value === 'undefined') return '-';
2053             if(typeof value !== 'number') return value;
2054
2055             var abs = Math.abs(value);
2056             if(abs >= 1000) return (Math.round(value)).toLocaleString();
2057             if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2058             if(abs >= 1   ) return (Math.round(value * 100) / 100).toLocaleString();
2059             if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2060             return (Math.round(value * 10000) / 10000).toLocaleString();
2061         };
2062
2063         this.legendSetLabelValue = function(label, value) {
2064             var series = this.element_legend_childs.series[label];
2065             if(typeof series === 'undefined') return;
2066             if(series.value === null && series.user === null) return;
2067
2068             // if the value has not changed, skip DOM update
2069             //if(series.last === value) return;
2070
2071             var s, r;
2072             if(typeof value === 'number') {
2073                 var v = Math.abs(value);
2074                 s = r = this.legendFormatValue(value);
2075
2076                 if(typeof series.last === 'number') {
2077                     if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2078                     else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2079                     else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2080                 }
2081                 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2082                 series.last = v;
2083             }
2084             else {
2085                 s = r = value;
2086                 series.last = value;
2087             }
2088
2089             if(series.value !== null) series.value.innerHTML = s;
2090             if(series.user !== null) series.user.innerHTML = r;
2091         };
2092
2093         this.legendSetDate = function(ms) {
2094             if(typeof ms !== 'number') {
2095                 this.legendShowUndefined();
2096                 return;
2097             }
2098
2099             var d = new Date(ms);
2100
2101             if(this.element_legend_childs.title_date)
2102                 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2103
2104             if(this.element_legend_childs.title_time)
2105                 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2106
2107             if(this.element_legend_childs.title_units)
2108                 this.element_legend_childs.title_units.innerHTML = this.units;
2109         };
2110
2111         this.legendShowUndefined = function() {
2112             if(this.element_legend_childs.title_date)
2113                 this.element_legend_childs.title_date.innerHTML = '&nbsp;';
2114
2115             if(this.element_legend_childs.title_time)
2116                 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2117
2118             if(this.element_legend_childs.title_units)
2119                 this.element_legend_childs.title_units.innerHTML = '&nbsp;';
2120
2121             if(this.data && this.element_legend_childs.series !== null) {
2122                 var labels = this.data.dimension_names;
2123                 var i = labels.length;
2124                 while(i--) {
2125                     var label = labels[i];
2126
2127                     if(typeof label === 'undefined') continue;
2128                     if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2129                     this.legendSetLabelValue(label, null);
2130                 }
2131             }
2132         };
2133
2134         this.legendShowLatestValues = function() {
2135             if(this.chart === null) return;
2136             if(this.selected) return;
2137
2138             if(this.data === null || this.element_legend_childs.series === null) {
2139                 this.legendShowUndefined();
2140                 return;
2141             }
2142
2143             var show_undefined = true;
2144             if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2145                 show_undefined = false;
2146
2147             if(show_undefined) {
2148                 this.legendShowUndefined();
2149                 return;
2150             }
2151
2152             this.legendSetDate(this.view_before);
2153
2154             var labels = this.data.dimension_names;
2155             var i = labels.length;
2156             while(i--) {
2157                 var label = labels[i];
2158
2159                 if(typeof label === 'undefined') continue;
2160                 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2161
2162                 if(show_undefined)
2163                     this.legendSetLabelValue(label, null);
2164                 else
2165                     this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2166             }
2167         };
2168
2169         this.legendReset = function() {
2170             this.legendShowLatestValues();
2171         };
2172
2173         // this should be called just ONCE per dimension per chart
2174         this._chartDimensionColor = function(label) {
2175             if(this.colors === null) this.chartColors();
2176
2177             if(typeof this.colors_assigned[label] === 'undefined') {
2178                 if(this.colors_available.length === 0) {
2179                     var len = NETDATA.themes.current.colors.length;
2180                     while(len--)
2181                         this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2182                 }
2183
2184                 this.colors_assigned[label] = this.colors_available.shift();
2185
2186                 if(this.debug === true)
2187                     this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2188             }
2189             else {
2190                 if(this.debug === true)
2191                     this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2192             }
2193
2194             this.colors.push(this.colors_assigned[label]);
2195             return this.colors_assigned[label];
2196         };
2197
2198         this.chartColors = function() {
2199             if(this.colors !== null) return this.colors;
2200
2201             this.colors = new Array();
2202             this.colors_available = new Array();
2203
2204             // add the standard colors
2205             var len = NETDATA.themes.current.colors.length;
2206             while(len--)
2207                 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2208
2209             // add the user supplied colors
2210             var c = $(this.element).data('colors');
2211             // this.log('read colors: ' + c);
2212             if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2213                 if(typeof c !== 'string') {
2214                     this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2215                 }
2216                 else {
2217                     c = c.split(' ');
2218                     var added = 0;
2219
2220                     while(added < 20) {
2221                         len = c.length;
2222                         while(len--) {
2223                             added++;
2224                             this.colors_available.unshift(c[len]);
2225                             // this.log('adding color: ' + c[len]);
2226                         }
2227                     }
2228                 }
2229             }
2230
2231             return this.colors;
2232         };
2233
2234         this.legendUpdateDOM = function() {
2235             var needed = false;
2236
2237             // check that the legend DOM is up to date for the downloaded dimensions
2238             if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2239                 // this.log('the legend does not have any series - requesting legend update');
2240                 needed = true;
2241             }
2242             else if(this.data === null) {
2243                 // this.log('the chart does not have any data - requesting legend update');
2244                 needed = true;
2245             }
2246             else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2247                 needed = true;
2248             }
2249             else {
2250                 var labels = this.data.dimension_names.toString();
2251                 if(labels !== this.element_legend_childs.series.labels_key) {
2252                     needed = true;
2253
2254                     if(this.debug === true)
2255                         this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2256                 }
2257             }
2258
2259             if(needed === false) {
2260                 // make sure colors available
2261                 this.chartColors();
2262
2263                 // do we have to update the current values?
2264                 // we do this, only when the visible chart is current
2265                 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2266                     if(this.debug === true)
2267                         this.log('chart is in latest position... updating values on legend...');
2268
2269                     //var labels = this.data.dimension_names;
2270                     //var i = labels.length;
2271                     //while(i--)
2272                     //  this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2273                 }
2274                 return;
2275             }
2276             if(this.colors === null) {
2277                 // this is the first time we update the chart
2278                 // let's assign colors to all dimensions
2279                 if(this.library.track_colors() === true)
2280                     for(var dim in this.chart.dimensions)
2281                         this._chartDimensionColor(this.chart.dimensions[dim].name);
2282             }
2283             // we will re-generate the colors for the chart
2284             // based on the selected dimensions
2285             this.colors = null;
2286
2287             if(this.debug === true)
2288                 this.log('updating Legend DOM');
2289
2290             // mark all dimensions as invalid
2291             this.dimensions_visibility.invalidateAll();
2292
2293             var genLabel = function(state, parent, dim, name, count) {
2294                 var color = state._chartDimensionColor(name);
2295
2296                 var user_element = null;
2297                 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2298                 if(user_id !== null) {
2299                     user_element = document.getElementById(user_id) || null;
2300                     if(user_element === null)
2301                         state.log('Cannot find element with id: ' + user_id);
2302                 }
2303
2304                 state.element_legend_childs.series[name] = {
2305                     name: document.createElement('span'),
2306                     value: document.createElement('span'),
2307                     user: user_element,
2308                     last: null
2309                 };
2310
2311                 var label = state.element_legend_childs.series[name];
2312
2313                 // create the dimension visibility tracking for this label
2314                 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2315
2316                 var rgb = NETDATA.colorHex2Rgb(color);
2317                 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2318                     + state.chart.chart_type
2319                     + '" style="background-color: '
2320                     + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2321                     + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2322
2323                 var text = document.createTextNode(' ' + name);
2324                 label.name.appendChild(text);
2325
2326                 if(count > 0)
2327                     parent.appendChild(document.createElement('br'));
2328
2329                 parent.appendChild(label.name);
2330                 parent.appendChild(label.value);
2331             };
2332
2333             var content = document.createElement('div');
2334
2335             if(this.hasLegend()) {
2336                 this.element_legend_childs = {
2337                     content: content,
2338                     resize_handler: document.createElement('div'),
2339                     toolbox: document.createElement('div'),
2340                     toolbox_left: document.createElement('div'),
2341                     toolbox_right: document.createElement('div'),
2342                     toolbox_reset: document.createElement('div'),
2343                     toolbox_zoomin: document.createElement('div'),
2344                     toolbox_zoomout: document.createElement('div'),
2345                     toolbox_volume: document.createElement('div'),
2346                     title_date: document.createElement('span'),
2347                     title_time: document.createElement('span'),
2348                     title_units: document.createElement('span'),
2349                     nano: document.createElement('div'),
2350                     nano_options: {
2351                         paneClass: 'netdata-legend-series-pane',
2352                         sliderClass: 'netdata-legend-series-slider',
2353                         contentClass: 'netdata-legend-series-content',
2354                         enabledClass: '__enabled',
2355                         flashedClass: '__flashed',
2356                         activeClass: '__active',
2357                         tabIndex: -1,
2358                         alwaysVisible: true,
2359                         sliderMinHeight: 10
2360                     },
2361                     series: {}
2362                 };
2363
2364                 this.element_legend.innerHTML = '';
2365
2366                 if(this.library.toolboxPanAndZoom !== null) {
2367
2368                     function get_pan_and_zoom_step(event) {
2369                         if (event.ctrlKey)
2370                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2371
2372                         else if (event.shiftKey)
2373                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2374
2375                         else if (event.altKey)
2376                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2377
2378                         else
2379                             return NETDATA.options.current.pan_and_zoom_factor;
2380                     }
2381
2382                     this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2383                     this.element.appendChild(this.element_legend_childs.toolbox);
2384
2385                     this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2386                     this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2387                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2388                     this.element_legend_childs.toolbox_left.onclick = function(e) {
2389                         e.preventDefault();
2390
2391                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2392                         var before = that.view_before - step;
2393                         var after = that.view_after - step;
2394                         if(after >= that.netdata_first)
2395                             that.library.toolboxPanAndZoom(that, after, before);
2396                     };
2397                     if(NETDATA.options.current.show_help === true)
2398                         $(this.element_legend_childs.toolbox_left).popover({
2399                         container: "body",
2400                         animation: false,
2401                         html: true,
2402                         trigger: 'hover',
2403                         placement: 'bottom',
2404                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2405                         title: 'Pan Left',
2406                         content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2407                     });
2408
2409
2410                     this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2411                     this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2412                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2413                     this.element_legend_childs.toolbox_reset.onclick = function(e) {
2414                         e.preventDefault();
2415                         NETDATA.resetAllCharts(that);
2416                     };
2417                     if(NETDATA.options.current.show_help === true)
2418                         $(this.element_legend_childs.toolbox_reset).popover({
2419                         container: "body",
2420                         animation: false,
2421                         html: true,
2422                         trigger: 'hover',
2423                         placement: 'bottom',
2424                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2425                         title: 'Chart Reset',
2426                         content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2427                     });
2428                     
2429                     this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2430                     this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2431                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2432                     this.element_legend_childs.toolbox_right.onclick = function(e) {
2433                         e.preventDefault();
2434                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2435                         var before = that.view_before + step;
2436                         var after = that.view_after + step;
2437                         if(before <= that.netdata_last)
2438                             that.library.toolboxPanAndZoom(that, after, before);
2439                     };
2440                     if(NETDATA.options.current.show_help === true)
2441                         $(this.element_legend_childs.toolbox_right).popover({
2442                         container: "body",
2443                         animation: false,
2444                         html: true,
2445                         trigger: 'hover',
2446                         placement: 'bottom',
2447                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2448                         title: 'Pan Right',
2449                         content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2450                     });
2451
2452                     
2453                     this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2454                     this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2455                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2456                     this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2457                         e.preventDefault();
2458                         var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2459                         var before = that.view_before - dt;
2460                         var after = that.view_after + dt;
2461                         that.library.toolboxPanAndZoom(that, after, before);
2462                     };
2463                     if(NETDATA.options.current.show_help === true)
2464                         $(this.element_legend_childs.toolbox_zoomin).popover({
2465                         container: "body",
2466                         animation: false,
2467                         html: true,
2468                         trigger: 'hover',
2469                         placement: 'bottom',
2470                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2471                         title: 'Chart Zoom In',
2472                         content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2473                     });
2474                     
2475                     this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2476                     this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2477                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2478                     this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2479                         e.preventDefault();
2480                         var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2481                         var before = that.view_before + dt;
2482                         var after = that.view_after - dt;
2483
2484                         that.library.toolboxPanAndZoom(that, after, before);
2485                     };
2486                     if(NETDATA.options.current.show_help === true)
2487                         $(this.element_legend_childs.toolbox_zoomout).popover({
2488                         container: "body",
2489                         animation: false,
2490                         html: true,
2491                         trigger: 'hover',
2492                         placement: 'bottom',
2493                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2494                         title: 'Chart Zoom Out',
2495                         content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2496                     });
2497                     
2498                     //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2499                     //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2500                     //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2501                     //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2502                     //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2503                         //e.preventDefault();
2504                         //alert('clicked toolbox_volume on ' + that.id);
2505                     //}
2506                 }
2507                 else {
2508                     this.element_legend_childs.toolbox = null;
2509                     this.element_legend_childs.toolbox_left = null;
2510                     this.element_legend_childs.toolbox_reset = null;
2511                     this.element_legend_childs.toolbox_right = null;
2512                     this.element_legend_childs.toolbox_zoomin = null;
2513                     this.element_legend_childs.toolbox_zoomout = null;
2514                     this.element_legend_childs.toolbox_volume = null;
2515                 }
2516                 
2517                 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2518                 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2519                 this.element.appendChild(this.element_legend_childs.resize_handler);
2520                 if(NETDATA.options.current.show_help === true)
2521                     $(this.element_legend_childs.resize_handler).popover({
2522                     container: "body",
2523                     animation: false,
2524                     html: true,
2525                     trigger: 'hover',
2526                     placement: 'bottom',
2527                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2528                     title: 'Chart Resize',
2529                     content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2530                 });
2531
2532                 // mousedown event
2533                 this.element_legend_childs.resize_handler.onmousedown =
2534                     function(e) {
2535                         that.resizeHandler(e);
2536                     };
2537
2538                 // touchstart event
2539                 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2540                     that.resizeHandler(e);
2541                 }, false);
2542
2543                 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2544                 this.element_legend.appendChild(this.element_legend_childs.title_date);
2545
2546                 this.element_legend.appendChild(document.createElement('br'));
2547
2548                 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2549                 this.element_legend.appendChild(this.element_legend_childs.title_time);
2550
2551                 this.element_legend.appendChild(document.createElement('br'));
2552
2553                 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2554                 this.element_legend.appendChild(this.element_legend_childs.title_units);
2555
2556                 this.element_legend.appendChild(document.createElement('br'));
2557
2558                 this.element_legend_childs.nano.className = 'netdata-legend-series';
2559                 this.element_legend.appendChild(this.element_legend_childs.nano);
2560
2561                 content.className = 'netdata-legend-series-content';
2562                 this.element_legend_childs.nano.appendChild(content);
2563
2564                 if(NETDATA.options.current.show_help === true)
2565                     $(content).popover({
2566                     container: "body",
2567                     animation: false,
2568                     html: true,
2569                     trigger: 'hover',
2570                     placement: 'bottom',
2571                     title: 'Chart Legend',
2572                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2573                     content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2574                 });
2575             }
2576             else {
2577                 this.element_legend_childs = {
2578                     content: content,
2579                     resize_handler: null,
2580                     toolbox: null,
2581                     toolbox_left: null,
2582                     toolbox_right: null,
2583                     toolbox_reset: null,
2584                     toolbox_zoomin: null,
2585                     toolbox_zoomout: null,
2586                     toolbox_volume: null,
2587                     title_date: null,
2588                     title_time: null,
2589                     title_units: null,
2590                     nano: null,
2591                     nano_options: null,
2592                     series: {}
2593                 };
2594             }
2595
2596             if(this.data) {
2597                 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2598                 if(this.debug === true)
2599                     this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2600
2601                 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2602                     genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2603                 }
2604             }
2605             else {
2606                 var tmp = new Array();
2607                 for(var dim in this.chart.dimensions) {
2608                     tmp.push(this.chart.dimensions[dim].name);
2609                     genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2610                 }
2611                 this.element_legend_childs.series.labels_key = tmp.toString();
2612                 if(this.debug === true)
2613                     this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2614             }
2615
2616             // create a hidden div to be used for hidding
2617             // the original legend of the chart library
2618             var el = document.createElement('div');
2619             if(this.element_legend !== null)
2620                 this.element_legend.appendChild(el);
2621             el.style.display = 'none';
2622
2623             this.element_legend_childs.hidden = document.createElement('div');
2624             el.appendChild(this.element_legend_childs.hidden);
2625
2626             if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2627                 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2628
2629             this.legendShowLatestValues();
2630         };
2631
2632         this.hasLegend = function() {
2633             if(typeof this.___hasLegendCache___ !== 'undefined')
2634                 return this.___hasLegendCache___;
2635
2636             var leg = false;
2637             if(this.library && this.library.legend(this) === 'right-side') {
2638                 var legend = $(this.element).data('legend') || 'yes';
2639                 if(legend === 'yes') leg = true;
2640             }
2641
2642             this.___hasLegendCache___ = leg;
2643             return leg;
2644         };
2645
2646         this.legendWidth = function() {
2647             return (this.hasLegend())?140:0;
2648         };
2649
2650         this.legendHeight = function() {
2651             return $(this.element).height();
2652         };
2653
2654         this.chartWidth = function() {
2655             return $(this.element).width() - this.legendWidth();
2656         };
2657
2658         this.chartHeight = function() {
2659             return $(this.element).height();
2660         };
2661
2662         this.chartPixelsPerPoint = function() {
2663             // force an options provided detail
2664             var px = this.pixels_per_point;
2665
2666             if(this.library && px < this.library.pixels_per_point(this))
2667                 px = this.library.pixels_per_point(this);
2668
2669             if(px < NETDATA.options.current.pixels_per_point)
2670                 px = NETDATA.options.current.pixels_per_point;
2671
2672             return px;
2673         };
2674
2675         this.needsRecreation = function() {
2676             return (
2677                     this.chart_created === true
2678                     && this.library
2679                     && this.library.autoresize() === false
2680                     && this.tm.last_resized < NETDATA.options.last_resized
2681                 );
2682         };
2683
2684         this.chartURL = function() {
2685             var after, before, points_multiplier = 1;
2686             if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2687                 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2688
2689                 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2690                 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2691                 this.view_after = after * 1000;
2692                 this.view_before = before * 1000;
2693
2694                 this.requested_padding = null;
2695                 points_multiplier = 1;
2696             }
2697             else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2698                 this.tm.pan_and_zoom_seq = 0;
2699
2700                 before = Math.round(this.current.force_before_ms / 1000);
2701                 after  = Math.round(this.current.force_after_ms / 1000);
2702                 this.view_after = after * 1000;
2703                 this.view_before = before * 1000;
2704
2705                 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2706                     this.requested_padding = Math.round((before - after) / 2);
2707                     after -= this.requested_padding;
2708                     before += this.requested_padding;
2709                     this.requested_padding *= 1000;
2710                     points_multiplier = 2;
2711                 }
2712
2713                 this.current.force_before_ms = null;
2714                 this.current.force_after_ms = null;
2715             }
2716             else {
2717                 this.tm.pan_and_zoom_seq = 0;
2718
2719                 before = this.before;
2720                 after  = this.after;
2721                 this.view_after = after * 1000;
2722                 this.view_before = before * 1000;
2723
2724                 this.requested_padding = null;
2725                 points_multiplier = 1;
2726             }
2727
2728             this.requested_after = after * 1000;
2729             this.requested_before = before * 1000;
2730
2731             this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2732
2733             // build the data URL
2734             this.data_url = this.host + this.chart.data_url;
2735             this.data_url += "&format="  + this.library.format();
2736             this.data_url += "&points="  + (this.data_points * points_multiplier).toString();
2737             this.data_url += "&group="   + this.method;
2738             this.data_url += "&options=" + this.library.options(this);
2739             this.data_url += '|jsonwrap';
2740
2741             if(NETDATA.options.current.eliminate_zero_dimensions === true)
2742                 this.data_url += '|nonzero';
2743
2744             if(this.append_options !== null)
2745                 this.data_url += '|' + this.append_options.toString();
2746
2747             if(after)
2748                 this.data_url += "&after="  + after.toString();
2749
2750             if(before)
2751                 this.data_url += "&before=" + before.toString();
2752
2753             if(this.dimensions)
2754                 this.data_url += "&dimensions=" + this.dimensions;
2755
2756             if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2757                 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2758         };
2759
2760         this.redrawChart = function() {
2761             if(this.data !== null)
2762                 this.updateChartWithData(this.data);
2763         };
2764
2765         this.updateChartWithData = function(data) {
2766             if(this.debug === true)
2767                 this.log('updateChartWithData() called.');
2768
2769             // this may force the chart to be re-created
2770             resizeChart();
2771
2772             this.data = data;
2773             this.updates_counter++;
2774             this.updates_since_last_unhide++;
2775             this.updates_since_last_creation++;
2776
2777             var started = new Date().getTime();
2778
2779             // if the result is JSON, find the latest update-every
2780             this.data_update_every = data.view_update_every * 1000;
2781             this.data_after = data.after * 1000;
2782             this.data_before = data.before * 1000;
2783             this.netdata_first = data.first_entry * 1000;
2784             this.netdata_last = data.last_entry * 1000;
2785             this.data_points = data.points;
2786             data.state = this;
2787
2788             if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2789                 if(this.view_after < this.data_after) {
2790                     // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2791                     this.view_after = this.data_after;
2792                 }
2793
2794                 if(this.view_before > this.data_before) {
2795                     // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2796                     this.view_before = this.data_before;
2797                 }
2798             }
2799             else {
2800                 this.view_after = this.data_after;
2801                 this.view_before = this.data_before;
2802             }
2803
2804             if(this.debug === true) {
2805                 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2806
2807                 if(this.current.force_after_ms)
2808                     this.log('STATUS: forced    : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2809                 else
2810                     this.log('STATUS: forced    : unset');
2811
2812                 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2813                 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2814                 this.log('STATUS: rendered  : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2815                 this.log('STATUS: points    : ' + (this.data_points).toString());
2816             }
2817
2818             if(this.data_points === 0) {
2819                 noDataToShow();
2820                 return;
2821             }
2822
2823             if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2824                 if(this.debug === true)
2825                     this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2826
2827                 this.chart_created = false;
2828             }
2829
2830             // check and update the legend
2831             this.legendUpdateDOM();
2832
2833             if(this.chart_created === true
2834                 && typeof this.library.update === 'function') {
2835
2836                 if(this.debug === true)
2837                     this.log('updating chart...');
2838
2839                 if(callChartLibraryUpdateSafely(data) === false)
2840                     return;
2841             }
2842             else {
2843                 if(this.debug === true)
2844                     this.log('creating chart...');
2845
2846                 if(callChartLibraryCreateSafely(data) === false)
2847                     return;
2848             }
2849             hideMessage();
2850             this.legendShowLatestValues();
2851             if(this.selected === true)
2852                 NETDATA.globalSelectionSync.stop();
2853
2854             // update the performance counters
2855             var now = new Date().getTime();
2856             this.tm.last_updated = now;
2857
2858             // don't update last_autorefreshed if this chart is
2859             // forced to be updated with global PanAndZoom
2860             if(NETDATA.globalPanAndZoom.isActive())
2861                 this.tm.last_autorefreshed = 0;
2862             else {
2863                 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2864                     this.tm.last_autorefreshed = now - (now % this.data_update_every);
2865                 else
2866                     this.tm.last_autorefreshed = now;
2867             }
2868
2869             this.refresh_dt_ms = now - started;
2870             NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2871
2872             if(this.refresh_dt_element !== null)
2873                 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2874         };
2875
2876         this.updateChart = function(callback) {
2877             if(this.debug === true)
2878                 this.log('updateChart() called.');
2879
2880             if(this._updating === true) {
2881                 if(this.debug === true)
2882                     this.log('I am already updating...');
2883
2884                 if(typeof callback === 'function') callback();
2885                 return false;
2886             }
2887
2888             // due to late initialization of charts and libraries
2889             // we need to check this too
2890             if(this.enabled === false) {
2891                 if(this.debug === true)
2892                     this.log('I am not enabled');
2893
2894                 if(typeof callback === 'function') callback();
2895                 return false;
2896             }
2897
2898             if(canBeRendered() === false) {
2899                 if(typeof callback === 'function') callback();
2900                 return false;
2901             }
2902
2903             if(this.chart === null) {
2904                 this.getChart(function() { that.updateChart(callback); });
2905                 return false;
2906             }
2907
2908             if(this.library.initialized === false) {
2909                 if(this.library.enabled === true) {
2910                     this.library.initialize(function() { that.updateChart(callback); });
2911                     return false;
2912                 }
2913                 else {
2914                     error('chart library "' + this.library_name + '" is not available.');
2915                     if(typeof callback === 'function') callback();
2916                     return false;
2917                 }
2918             }
2919
2920             this.clearSelection();
2921             this.chartURL();
2922
2923             if(this.debug === true)
2924                 this.log('updating from ' + this.data_url);
2925
2926             NETDATA.statistics.refreshes_total++;
2927             NETDATA.statistics.refreshes_active++;
2928
2929             if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2930                 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2931
2932             this._updating = true;
2933
2934             this.xhr = $.ajax( {
2935                 url: this.data_url,
2936                 cache: false,
2937                 async: true,
2938                 headers: {
2939                     'Cache-Control': 'no-cache, no-store',
2940                     'Pragma': 'no-cache'
2941                 },
2942                 xhrFields: { withCredentials: true } // required for the cookie
2943             })
2944             .done(function(data) {
2945                 that.xhr = undefined;
2946
2947                 if(that.debug === true)
2948                     that.log('data received. updating chart.');
2949
2950                 that.updateChartWithData(data);
2951             })
2952             .fail(function(msg) {
2953                 that.xhr = undefined;
2954
2955                 if(msg.statusText !== 'abort')
2956                     error('data download failed for url: ' + that.data_url);
2957             })
2958             .always(function() {
2959                 that.xhr = undefined;
2960
2961                 NETDATA.statistics.refreshes_active--;
2962                 that._updating = false;
2963                 if(typeof callback === 'function') callback();
2964             });
2965
2966             return true;
2967         };
2968
2969         this.isVisible = function(nocache) {
2970             if(typeof nocache === 'undefined')
2971                 nocache = false;
2972
2973             // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2974
2975             // caching - we do not evaluate the charts visibility
2976             // if the page has not been scrolled since the last check
2977             if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2978                 return this.___isVisible___;
2979
2980             this.tm.last_visible_check = new Date().getTime();
2981
2982             var wh = window.innerHeight;
2983             var x = this.element.getBoundingClientRect();
2984             var ret = 0;
2985             var tolerance = 0;
2986
2987             if(x.width === 0 || x.height === 0) {
2988                 hideChart();
2989                 this.___isVisible___ = false;
2990                 return this.___isVisible___;
2991             }
2992
2993             if(x.top < 0 && -x.top > x.height) {
2994                 // the chart is entirely above
2995                 ret = -x.top - x.height;
2996             }
2997             else if(x.top > wh) {
2998                 // the chart is entirely below
2999                 ret = x.top - wh;
3000             }
3001
3002             if(ret > tolerance) {
3003                 // the chart is too far
3004
3005                 hideChart();
3006                 this.___isVisible___ = false;
3007                 return this.___isVisible___;
3008             }
3009             else {
3010                 // the chart is inside or very close
3011
3012                 unhideChart();
3013                 this.___isVisible___ = true;
3014                 return this.___isVisible___;
3015             }
3016         };
3017
3018         this.isAutoRefreshable = function() {
3019             return (this.current.autorefresh);
3020         };
3021
3022         this.canBeAutoRefreshed = function() {
3023             var now = new Date().getTime();
3024
3025             if(this.running === true) {
3026                 if(this.debug === true)
3027                     this.log('I am already running');
3028
3029                 return false;
3030             }
3031
3032             if(this.enabled === false) {
3033                 if(this.debug === true)
3034                     this.log('I am not enabled');
3035
3036                 return false;
3037             }
3038
3039             if(this.library === null || this.library.enabled === false) {
3040                 error('charting library "' + this.library_name + '" is not available');
3041                 if(this.debug === true)
3042                     this.log('My chart library ' + this.library_name + ' is not available');
3043
3044                 return false;
3045             }
3046
3047             if(this.isVisible() === false) {
3048                 if(NETDATA.options.debug.visibility === true || this.debug === true)
3049                     this.log('I am not visible');
3050
3051                 return false;
3052             }
3053
3054             if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3055                 if(this.debug === true)
3056                     this.log('timed force update detected - allowing this update');
3057
3058                 this.current.force_update_at = 0;
3059                 return true;
3060             }
3061
3062             if(this.isAutoRefreshable() === true) {
3063                 // allow the first update, even if the page is not visible
3064                 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3065                     if(NETDATA.options.debug.focus === true || this.debug === true)
3066                         this.log('canBeAutoRefreshed(): page does not have focus');
3067
3068                     return false;
3069                 }
3070
3071                 if(this.needsRecreation() === true) {
3072                     if(this.debug === true)
3073                         this.log('canBeAutoRefreshed(): needs re-creation.');
3074
3075                     return true;
3076                 }
3077
3078                 // options valid only for autoRefresh()
3079                 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3080                     if(NETDATA.globalPanAndZoom.isActive()) {
3081                         if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3082                             if(this.debug === true)
3083                                 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3084
3085                             return true;
3086                         }
3087                         else {
3088                             if(this.debug === true)
3089                                 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3090
3091                             return false;
3092                         }
3093                     }
3094
3095                     if(this.selected === true) {
3096                         if(this.debug === true)
3097                             this.log('canBeAutoRefreshed(): I have a selection in place.');
3098
3099                         return false;
3100                     }
3101
3102                     if(this.paused === true) {
3103                         if(this.debug === true)
3104                             this.log('canBeAutoRefreshed(): I am paused.');
3105
3106                         return false;
3107                     }
3108
3109                     if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3110                         if(this.debug === true)
3111                             this.log('canBeAutoRefreshed(): It is time to update me.');
3112
3113                         return true;
3114                     }
3115                 }
3116             }
3117
3118             return false;
3119         };
3120
3121         this.autoRefresh = function(callback) {
3122             if(this.canBeAutoRefreshed() === true && this.running === false) {
3123                 var state = this;
3124
3125                 state.running = true;
3126                 state.updateChart(function() {
3127                     state.running = false;
3128
3129                     if(typeof callback !== 'undefined')
3130                         callback();
3131                 });
3132             }
3133             else {
3134                 if(typeof callback !== 'undefined')
3135                     callback();
3136             }
3137         };
3138
3139         this._defaultsFromDownloadedChart = function(chart) {
3140             this.chart = chart;
3141             this.chart_url = chart.url;
3142             this.data_update_every = chart.update_every * 1000;
3143             this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3144             this.tm.last_info_downloaded = new Date().getTime();
3145
3146             if(this.title === null)
3147                 this.title = chart.title;
3148
3149             if(this.units === null)
3150                 this.units = chart.units;
3151         };
3152
3153         // fetch the chart description from the netdata server
3154         this.getChart = function(callback) {
3155             this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3156             if(this.chart) {
3157                 this._defaultsFromDownloadedChart(this.chart);
3158                 if(typeof callback === 'function') callback();
3159             }
3160             else {
3161                 this.chart_url = "/api/v1/chart?chart=" + this.id;
3162
3163                 if(this.debug === true)
3164                     this.log('downloading ' + this.chart_url);
3165
3166                 $.ajax( {
3167                     url:  this.host + this.chart_url,
3168                     cache: false,
3169                     async: true,
3170                     xhrFields: { withCredentials: true } // required for the cookie
3171                 })
3172                 .done(function(chart) {
3173                     chart.url = that.chart_url;
3174                     that._defaultsFromDownloadedChart(chart);
3175                     NETDATA.chartRegistry.add(that.host, that.id, chart);
3176                 })
3177                 .fail(function() {
3178                     NETDATA.error(404, that.chart_url);
3179                     error('chart not found on url "' + that.chart_url + '"');
3180                 })
3181                 .always(function() {
3182                     if(typeof callback === 'function') callback();
3183                 });
3184             }
3185         };
3186
3187         // ============================================================================================================
3188         // INITIALIZATION
3189
3190         init();
3191     };
3192
3193     NETDATA.resetAllCharts = function(state) {
3194         // first clear the global selection sync
3195         // to make sure no chart is in selected state
3196         state.globalSelectionSyncStop();
3197
3198         // there are 2 possibilities here
3199         // a. state is the global Pan and Zoom master
3200         // b. state is not the global Pan and Zoom master
3201         var master = true;
3202         if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3203             master = false;
3204
3205         // clear the global Pan and Zoom
3206         // this will also refresh the master
3207         // and unblock any charts currently mirroring the master
3208         NETDATA.globalPanAndZoom.clearMaster();
3209
3210         // if we were not the master, reset our status too
3211         // this is required because most probably the mouse
3212         // is over this chart, blocking it from auto-refreshing
3213         if(master === false && (state.paused === true || state.selected === true))
3214             state.resetChart();
3215     };
3216
3217     // get or create a chart state, given a DOM element
3218     NETDATA.chartState = function(element) {
3219         var state = $(element).data('netdata-state-object') || null;
3220         if(state === null) {
3221             state = new chartState(element);
3222             $(element).data('netdata-state-object', state);
3223         }
3224         return state;
3225     };
3226
3227     // ----------------------------------------------------------------------------------------------------------------
3228     // Library functions
3229
3230     // Load a script without jquery
3231     // This is used to load jquery - after it is loaded, we use jquery
3232     NETDATA._loadjQuery = function(callback) {
3233         if(typeof jQuery === 'undefined') {
3234             if(NETDATA.options.debug.main_loop === true)
3235                 console.log('loading ' + NETDATA.jQuery);
3236
3237             var script = document.createElement('script');
3238             script.type = 'text/javascript';
3239             script.async = true;
3240             script.src = NETDATA.jQuery;
3241
3242             // script.onabort = onError;
3243             script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3244             if(typeof callback === "function")
3245                 script.onload = callback;
3246
3247             var s = document.getElementsByTagName('script')[0];
3248             s.parentNode.insertBefore(script, s);
3249         }
3250         else if(typeof callback === "function")
3251             callback();
3252     };
3253
3254     NETDATA._loadCSS = function(filename) {
3255         // don't use jQuery here
3256         // styles are loaded before jQuery
3257         // to eliminate showing an unstyled page to the user
3258
3259         var fileref = document.createElement("link");
3260         fileref.setAttribute("rel", "stylesheet");
3261         fileref.setAttribute("type", "text/css");
3262         fileref.setAttribute("href", filename);
3263
3264         if (typeof fileref !== 'undefined')
3265             document.getElementsByTagName("head")[0].appendChild(fileref);
3266     };
3267
3268     NETDATA.colorHex2Rgb = function(hex) {
3269         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3270         var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3271             hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3272             return r + r + g + g + b + b;
3273         });
3274
3275         var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3276         return result ? {
3277             r: parseInt(result[1], 16),
3278             g: parseInt(result[2], 16),
3279             b: parseInt(result[3], 16)
3280         } : null;
3281     };
3282
3283     NETDATA.colorLuminance = function(hex, lum) {
3284         // validate hex string
3285         hex = String(hex).replace(/[^0-9a-f]/gi, '');
3286         if (hex.length < 6)
3287             hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3288
3289         lum = lum || 0;
3290
3291         // convert to decimal and change luminosity
3292         var rgb = "#", c, i;
3293         for (i = 0; i < 3; i++) {
3294             c = parseInt(hex.substr(i*2,2), 16);
3295             c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3296             rgb += ("00"+c).substr(c.length);
3297         }
3298
3299         return rgb;
3300     };
3301
3302     NETDATA.guid = function() {
3303         function s4() {
3304             return Math.floor((1 + Math.random()) * 0x10000)
3305                     .toString(16)
3306                     .substring(1);
3307             }
3308
3309             return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3310     };
3311
3312     NETDATA.zeropad = function(x) {
3313         if(x > -10 && x < 10) return '0' + x.toString();
3314         else return x.toString();
3315     };
3316
3317     // user function to signal us the DOM has been
3318     // updated.
3319     NETDATA.updatedDom = function() {
3320         NETDATA.options.updated_dom = true;
3321     };
3322
3323     NETDATA.ready = function(callback) {
3324         NETDATA.options.pauseCallback = callback;
3325     };
3326
3327     NETDATA.pause = function(callback) {
3328         if(NETDATA.options.pause === true)
3329             callback();
3330         else
3331             NETDATA.options.pauseCallback = callback;
3332     };
3333
3334     NETDATA.unpause = function() {
3335         NETDATA.options.pauseCallback = null;
3336         NETDATA.options.updated_dom = true;
3337         NETDATA.options.pause = false;
3338     };
3339
3340     // ----------------------------------------------------------------------------------------------------------------
3341
3342     // this is purely sequencial charts refresher
3343     // it is meant to be autonomous
3344     NETDATA.chartRefresherNoParallel = function(index) {
3345         if(NETDATA.options.debug.mail_loop === true)
3346             console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3347
3348         if(NETDATA.options.updated_dom === true) {
3349             // the dom has been updated
3350             // get the dom parts again
3351             NETDATA.parseDom(NETDATA.chartRefresher);
3352             return;
3353         }
3354         if(index >= NETDATA.options.targets.length) {
3355             if(NETDATA.options.debug.main_loop === true)
3356                 console.log('waiting to restart main loop...');
3357
3358             NETDATA.options.auto_refresher_fast_weight = 0;
3359
3360             setTimeout(function() {
3361                 NETDATA.chartRefresher();
3362             }, NETDATA.options.current.idle_between_loops);
3363         }
3364         else {
3365             var state = NETDATA.options.targets[index];
3366
3367             if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3368                 if(NETDATA.options.debug.main_loop === true)
3369                     console.log('fast rendering...');
3370
3371                 state.autoRefresh(function() {
3372                     NETDATA.chartRefresherNoParallel(++index);
3373                 });
3374             }
3375             else {
3376                 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3377                 NETDATA.options.auto_refresher_fast_weight = 0;
3378
3379                 setTimeout(function() {
3380                     state.autoRefresh(function() {
3381                         NETDATA.chartRefresherNoParallel(++index);
3382                     });
3383                 }, NETDATA.options.current.idle_between_charts);
3384             }
3385         }
3386     };
3387
3388     // this is part of the parallel refresher
3389     // its cause is to refresh sequencially all the charts
3390     // that depend on chart library initialization
3391     // it will call the parallel refresher back
3392     // as soon as it sees a chart that its chart library
3393     // is initialized
3394     NETDATA.chartRefresher_uninitialized = function() {
3395         if(NETDATA.options.updated_dom === true) {
3396             // the dom has been updated
3397             // get the dom parts again
3398             NETDATA.parseDom(NETDATA.chartRefresher);
3399             return;
3400         }
3401
3402         if(NETDATA.options.sequencial.length === 0)
3403             NETDATA.chartRefresher();
3404         else {
3405             var state = NETDATA.options.sequencial.pop();
3406             if(state.library.initialized === true)
3407                 NETDATA.chartRefresher();
3408             else
3409                 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3410         }
3411     };
3412
3413     NETDATA.chartRefresherWaitTime = function() {
3414         return NETDATA.options.current.idle_parallel_loops;
3415     };
3416
3417     // the default refresher
3418     // it will create 2 sets of charts:
3419     // - the ones that can be refreshed in parallel
3420     // - the ones that depend on something else
3421     // the first set will be executed in parallel
3422     // the second will be given to NETDATA.chartRefresher_uninitialized()
3423     NETDATA.chartRefresher = function() {
3424         // console.log('auto-refresher...');
3425
3426         if(NETDATA.options.pause === true) {
3427             // console.log('auto-refresher is paused');
3428             setTimeout(NETDATA.chartRefresher,
3429                 NETDATA.chartRefresherWaitTime());
3430             return;
3431         }
3432
3433         if(typeof NETDATA.options.pauseCallback === 'function') {
3434             // console.log('auto-refresher is calling pauseCallback');
3435             NETDATA.options.pause = true;
3436             NETDATA.options.pauseCallback();
3437             NETDATA.chartRefresher();
3438             return;
3439         }
3440
3441         if(NETDATA.options.current.parallel_refresher === false) {
3442             // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3443             NETDATA.chartRefresherNoParallel(0);
3444             return;
3445         }
3446
3447         if(NETDATA.options.updated_dom === true) {
3448             // the dom has been updated
3449             // get the dom parts again
3450             // console.log('auto-refresher is calling parseDom()');
3451             NETDATA.parseDom(NETDATA.chartRefresher);
3452             return;
3453         }
3454
3455         var parallel = new Array();
3456         var targets = NETDATA.options.targets;
3457         var len = targets.length;
3458         var state;
3459         while(len--) {
3460             state = targets[len];
3461             if(state.isVisible() === false || state.running === true)
3462                 continue;
3463
3464             if(state.library.initialized === false) {
3465                 if(state.library.enabled === true) {
3466                     state.library.initialize(NETDATA.chartRefresher);
3467                     return;
3468                 }
3469                 else {
3470                     state.error('chart library "' + state.library_name + '" is not enabled.');
3471                 }
3472             }
3473
3474             parallel.unshift(state);
3475         }
3476
3477         if(parallel.length > 0) {
3478             // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3479             // this will execute the jobs in parallel
3480             $(parallel).each(function() {
3481                 this.autoRefresh();
3482             })
3483         }
3484         //else {
3485         //    console.log('auto-refresher nothing to do');
3486         //}
3487
3488         // run the next refresh iteration
3489         setTimeout(NETDATA.chartRefresher,
3490             NETDATA.chartRefresherWaitTime());
3491     };
3492
3493     NETDATA.parseDom = function(callback) {
3494         NETDATA.options.last_page_scroll = new Date().getTime();
3495         NETDATA.options.updated_dom = false;
3496
3497         var targets = $('div[data-netdata]'); //.filter(':visible');
3498
3499         if(NETDATA.options.debug.main_loop === true)
3500             console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3501
3502         NETDATA.options.targets = new Array();
3503         var len = targets.length;
3504         while(len--) {
3505             // the initialization will take care of sizing
3506             // and the "loading..." message
3507             NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3508         }
3509
3510         if(typeof callback === 'function') callback();
3511     };
3512
3513     // this is the main function - where everything starts
3514     NETDATA.start = function() {
3515         // this should be called only once
3516
3517         NETDATA.options.page_is_visible = true;
3518
3519         $(window).blur(function() {
3520             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3521                 NETDATA.options.page_is_visible = false;
3522                 if(NETDATA.options.debug.focus === true)
3523                     console.log('Lost Focus!');
3524             }
3525         });
3526
3527         $(window).focus(function() {
3528             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3529                 NETDATA.options.page_is_visible = true;
3530                 if(NETDATA.options.debug.focus === true)
3531                     console.log('Focus restored!');
3532             }
3533         });
3534
3535         if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3536             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3537                 NETDATA.options.page_is_visible = false;
3538                 if(NETDATA.options.debug.focus === true)
3539                     console.log('Document has no focus!');
3540             }
3541         }
3542
3543         // bootstrap tab switching
3544         $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3545
3546         // bootstrap modal switching
3547         $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3548         $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3549
3550         // bootstrap collapse switching
3551         $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3552         $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3553
3554         NETDATA.parseDom(NETDATA.chartRefresher);
3555
3556         // Alarms initialization
3557         setTimeout(NETDATA.alarms.init, 1000);
3558
3559         // Registry initialization
3560         setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3561
3562         if(typeof netdataCallback === 'function')
3563             netdataCallback();
3564     };
3565
3566     // ----------------------------------------------------------------------------------------------------------------
3567     // peity
3568
3569     NETDATA.peityInitialize = function(callback) {
3570         if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3571             $.ajax({
3572                 url: NETDATA.peity_js,
3573                 cache: true,
3574                 dataType: "script",
3575                 xhrFields: { withCredentials: true } // required for the cookie
3576             })
3577             .done(function() {
3578                 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3579             })
3580             .fail(function() {
3581                 NETDATA.chartLibraries.peity.enabled = false;
3582                 NETDATA.error(100, NETDATA.peity_js);
3583             })
3584             .always(function() {
3585                 if(typeof callback === "function")
3586                     callback();
3587             });
3588         }
3589         else {
3590             NETDATA.chartLibraries.peity.enabled = false;
3591             if(typeof callback === "function")
3592                 callback();
3593         }
3594     };
3595
3596     NETDATA.peityChartUpdate = function(state, data) {
3597         state.peity_instance.innerHTML = data.result;
3598
3599         if(state.peity_options.stroke !== state.chartColors()[0]) {
3600             state.peity_options.stroke = state.chartColors()[0];
3601             if(state.chart.chart_type === 'line')
3602                 state.peity_options.fill = NETDATA.themes.current.background;
3603             else
3604                 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3605         }
3606
3607         $(state.peity_instance).peity('line', state.peity_options);
3608         return true;
3609     };
3610
3611     NETDATA.peityChartCreate = function(state, data) {
3612         state.peity_instance = document.createElement('div');
3613         state.element_chart.appendChild(state.peity_instance);
3614
3615         var self = $(state.element);
3616         state.peity_options = {
3617             stroke: NETDATA.themes.current.foreground,
3618             strokeWidth: self.data('peity-strokewidth') || 1,
3619             width: state.chartWidth(),
3620             height: state.chartHeight(),
3621             fill: NETDATA.themes.current.foreground
3622         };
3623
3624         NETDATA.peityChartUpdate(state, data);
3625         return true;
3626     };
3627
3628     // ----------------------------------------------------------------------------------------------------------------
3629     // sparkline
3630
3631     NETDATA.sparklineInitialize = function(callback) {
3632         if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3633             $.ajax({
3634                 url: NETDATA.sparkline_js,
3635                 cache: true,
3636                 dataType: "script",
3637                 xhrFields: { withCredentials: true } // required for the cookie
3638             })
3639             .done(function() {
3640                 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3641             })
3642             .fail(function() {
3643                 NETDATA.chartLibraries.sparkline.enabled = false;
3644                 NETDATA.error(100, NETDATA.sparkline_js);
3645             })
3646             .always(function() {
3647                 if(typeof callback === "function")
3648                     callback();
3649             });
3650         }
3651         else {
3652             NETDATA.chartLibraries.sparkline.enabled = false;
3653             if(typeof callback === "function")
3654                 callback();
3655         }
3656     };
3657
3658     NETDATA.sparklineChartUpdate = function(state, data) {
3659         state.sparkline_options.width = state.chartWidth();
3660         state.sparkline_options.height = state.chartHeight();
3661
3662         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3663         return true;
3664     };
3665
3666     NETDATA.sparklineChartCreate = function(state, data) {
3667         var self = $(state.element);
3668         var type = self.data('sparkline-type') || 'line';
3669         var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3670         var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3671         var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3672         var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3673         var composite = self.data('sparkline-composite') || undefined;
3674         var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3675         var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3676         var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3677         var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3678         var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3679         var spotColor = self.data('sparkline-spotcolor') || undefined;
3680         var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3681         var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3682         var spotRadius = self.data('sparkline-spotradius') || undefined;
3683         var valueSpots = self.data('sparkline-valuespots') || undefined;
3684         var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3685         var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3686         var lineWidth = self.data('sparkline-linewidth') || undefined;
3687         var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3688         var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3689         var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3690         var xvalues = self.data('sparkline-xvalues') || undefined;
3691         var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3692         var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3693         var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3694         var disableInteraction = self.data('sparkline-disableinteraction') || false;
3695         var disableTooltips = self.data('sparkline-disabletooltips') || false;
3696         var disableHighlight = self.data('sparkline-disablehighlight') || false;
3697         var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3698         var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3699         var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3700         var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3701         var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3702         var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3703         var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3704         var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3705         var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3706         var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3707         var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3708         var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3709         var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3710         var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3711         var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3712         var animatedZooms = self.data('sparkline-animatedzooms') || false;
3713
3714         if(spotColor === 'disable') spotColor='';
3715         if(minSpotColor === 'disable') minSpotColor='';
3716         if(maxSpotColor === 'disable') maxSpotColor='';
3717
3718         state.sparkline_options = {
3719             type: type,
3720             lineColor: lineColor,
3721             fillColor: fillColor,
3722             chartRangeMin: chartRangeMin,
3723             chartRangeMax: chartRangeMax,
3724             composite: composite,
3725             enableTagOptions: enableTagOptions,
3726             tagOptionPrefix: tagOptionPrefix,
3727             tagValuesAttribute: tagValuesAttribute,
3728             disableHiddenCheck: disableHiddenCheck,
3729             defaultPixelsPerValue: defaultPixelsPerValue,
3730             spotColor: spotColor,
3731             minSpotColor: minSpotColor,
3732             maxSpotColor: maxSpotColor,
3733             spotRadius: spotRadius,
3734             valueSpots: valueSpots,
3735             highlightSpotColor: highlightSpotColor,
3736             highlightLineColor: highlightLineColor,
3737             lineWidth: lineWidth,
3738             normalRangeMin: normalRangeMin,
3739             normalRangeMax: normalRangeMax,
3740             drawNormalOnTop: drawNormalOnTop,
3741             xvalues: xvalues,
3742             chartRangeClip: chartRangeClip,
3743             chartRangeMinX: chartRangeMinX,
3744             chartRangeMaxX: chartRangeMaxX,
3745             disableInteraction: disableInteraction,
3746             disableTooltips: disableTooltips,
3747             disableHighlight: disableHighlight,
3748             highlightLighten: highlightLighten,
3749             highlightColor: highlightColor,
3750             tooltipContainer: tooltipContainer,
3751             tooltipClassname: tooltipClassname,
3752             tooltipChartTitle: state.title,
3753             tooltipFormat: tooltipFormat,
3754             tooltipPrefix: tooltipPrefix,
3755             tooltipSuffix: tooltipSuffix,
3756             tooltipSkipNull: tooltipSkipNull,
3757             tooltipValueLookups: tooltipValueLookups,
3758             tooltipFormatFieldlist: tooltipFormatFieldlist,
3759             tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3760             numberFormatter: numberFormatter,
3761             numberDigitGroupSep: numberDigitGroupSep,
3762             numberDecimalMark: numberDecimalMark,
3763             numberDigitGroupCount: numberDigitGroupCount,
3764             animatedZooms: animatedZooms,
3765             width: state.chartWidth(),
3766             height: state.chartHeight()
3767         };
3768
3769         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3770         return true;
3771     };
3772
3773     // ----------------------------------------------------------------------------------------------------------------
3774     // dygraph
3775
3776     NETDATA.dygraph = {
3777         smooth: false
3778     };
3779
3780     NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3781         if(after < state.netdata_first)
3782             after = state.netdata_first;
3783
3784         if(before > state.netdata_last)
3785             before = state.netdata_last;
3786
3787         state.setMode('zoom');
3788         state.globalSelectionSyncStop();
3789         state.globalSelectionSyncDelay();
3790         state.dygraph_user_action = true;
3791         state.dygraph_force_zoom = true;
3792         state.updateChartPanOrZoom(after, before);
3793         NETDATA.globalPanAndZoom.setMaster(state, after, before);
3794     };
3795
3796     NETDATA.dygraphSetSelection = function(state, t) {
3797         if(typeof state.dygraph_instance !== 'undefined') {
3798             var r = state.calculateRowForTime(t);
3799             if(r !== -1)
3800                 state.dygraph_instance.setSelection(r);
3801             else {
3802                 state.dygraph_instance.clearSelection();
3803                 state.legendShowUndefined();
3804             }
3805         }
3806
3807         return true;
3808     };
3809
3810     NETDATA.dygraphClearSelection = function(state, t) {
3811         if(typeof state.dygraph_instance !== 'undefined') {
3812             state.dygraph_instance.clearSelection();
3813         }
3814         return true;
3815     };
3816
3817     NETDATA.dygraphSmoothInitialize = function(callback) {
3818         $.ajax({
3819             url: NETDATA.dygraph_smooth_js,
3820             cache: true,
3821             dataType: "script",
3822             xhrFields: { withCredentials: true } // required for the cookie
3823         })
3824         .done(function() {
3825             NETDATA.dygraph.smooth = true;
3826             smoothPlotter.smoothing = 0.3;
3827         })
3828         .fail(function() {
3829             NETDATA.dygraph.smooth = false;
3830         })
3831         .always(function() {
3832             if(typeof callback === "function")
3833                 callback();
3834         });
3835     };
3836
3837     NETDATA.dygraphInitialize = function(callback) {
3838         if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3839             $.ajax({
3840                 url: NETDATA.dygraph_js,
3841                 cache: true,
3842                 dataType: "script",
3843                 xhrFields: { withCredentials: true } // required for the cookie
3844             })
3845             .done(function() {
3846                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3847             })
3848             .fail(function() {
3849                 NETDATA.chartLibraries.dygraph.enabled = false;
3850                 NETDATA.error(100, NETDATA.dygraph_js);
3851             })
3852             .always(function() {
3853                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3854                     NETDATA.dygraphSmoothInitialize(callback);
3855                 else if(typeof callback === "function")
3856                     callback();
3857             });
3858         }
3859         else {
3860             NETDATA.chartLibraries.dygraph.enabled = false;
3861             if(typeof callback === "function")
3862                 callback();
3863         }
3864     };
3865
3866     NETDATA.dygraphChartUpdate = function(state, data) {
3867         var dygraph = state.dygraph_instance;
3868
3869         if(typeof dygraph === 'undefined')
3870             return NETDATA.dygraphChartCreate(state, data);
3871
3872         // when the chart is not visible, and hidden
3873         // if there is a window resize, dygraph detects
3874         // its element size as 0x0.
3875         // this will make it re-appear properly
3876
3877         if(state.tm.last_unhidden > state.dygraph_last_rendered)
3878             dygraph.resize();
3879
3880         var options = {
3881                 file: data.result.data,
3882                 colors: state.chartColors(),
3883                 labels: data.result.labels,
3884                 labelsDivWidth: state.chartWidth() - 70,
3885                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3886         };
3887
3888         if(state.dygraph_force_zoom === true) {
3889             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3890                 state.log('dygraphChartUpdate() forced zoom update');
3891
3892             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3893             options.valueRange = state.dygraph_options.valueRange;
3894             options.isZoomedIgnoreProgrammaticZoom = true;
3895             state.dygraph_force_zoom = false;
3896         }
3897         else if(state.current.name !== 'auto') {
3898             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3899                 state.log('dygraphChartUpdate() loose update');
3900
3901             options.valueRange = state.dygraph_options.valueRange;
3902         }
3903         else {
3904             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3905                 state.log('dygraphChartUpdate() strict update');
3906
3907             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3908             options.valueRange = state.dygraph_options.valueRange;
3909             options.isZoomedIgnoreProgrammaticZoom = true;
3910         }
3911
3912         if(state.dygraph_smooth_eligible === true) {
3913             if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3914                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3915                 NETDATA.dygraphChartCreate(state, data);
3916                 return;
3917             }
3918         }
3919
3920         dygraph.updateOptions(options);
3921
3922         state.dygraph_last_rendered = new Date().getTime();
3923         return true;
3924     };
3925
3926     NETDATA.dygraphChartCreate = function(state, data) {
3927         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3928             state.log('dygraphChartCreate()');
3929
3930         var self = $(state.element);
3931
3932         var chart_type = state.chart.chart_type;
3933         if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3934         chart_type = self.data('dygraph-type') || chart_type;
3935
3936         var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3937         smooth = self.data('dygraph-smooth') || smooth;
3938
3939         if(NETDATA.dygraph.smooth === false)
3940             smooth = false;
3941
3942         var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3943         var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3944
3945         state.dygraph_options = {
3946             colors: self.data('dygraph-colors') || state.chartColors(),
3947
3948             // leave a few pixels empty on the right of the chart
3949             rightGap: self.data('dygraph-rightgap') || 5,
3950             showRangeSelector: self.data('dygraph-showrangeselector') || false,
3951             showRoller: self.data('dygraph-showroller') || false,
3952
3953             title: self.data('dygraph-title') || state.title,
3954             titleHeight: self.data('dygraph-titleheight') || 19,
3955
3956             legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3957             labels: data.result.labels,
3958             labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3959             labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3960             labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3961             labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3962             labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3963             labelsKMB: false,
3964             labelsKMG2: false,
3965             showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3966             hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3967
3968             includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
3969             xRangePad: self.data('dygraph-xrangepad') || 0,
3970             yRangePad: self.data('dygraph-yrangepad') || 1,
3971
3972             valueRange: self.data('dygraph-valuerange') || null,
3973
3974             ylabel: state.units,
3975             yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3976
3977             // the function to plot the chart
3978             plotter: null,
3979
3980             // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3981             strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3982             strokePattern: self.data('dygraph-strokepattern') || undefined,
3983
3984             // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3985             // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3986             drawPoints: self.data('dygraph-drawpoints') || false,
3987
3988             // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3989             drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3990
3991             connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3992             pointSize: self.data('dygraph-pointsize') || 1,
3993
3994             // enabling this makes the chart with little square lines
3995             stepPlot: self.data('dygraph-stepplot') || false,
3996
3997             // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3998             strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3999             strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4000
4001             fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4002             fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4003             stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4004             stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4005
4006             drawAxis: self.data('dygraph-drawaxis') || true,
4007             axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4008             axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4009             axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
4010
4011             drawGrid: self.data('dygraph-drawgrid') || true,
4012             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4013             gridLineWidth: self.data('dygraph-gridlinewidth') || 0.4,
4014             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4015
4016             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4017             sigFigs: self.data('dygraph-sigfigs') || null,
4018             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4019             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4020
4021             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4022             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4023             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4024
4025             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4026             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4027             axes: {
4028                 x: {
4029                     pixelsPerLabel: 50,
4030                     ticker: Dygraph.dateTicker,
4031                     axisLabelFormatter: function (d, gran) {
4032                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4033                     },
4034                     valueFormatter: function (ms) {
4035                         var d = new Date(ms);
4036                         return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4037                         // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4038                     }
4039                 },
4040                 y: {
4041                     pixelsPerLabel: 15,
4042                     valueFormatter: function (x) {
4043                         // we format legends with the state object
4044                         // no need to do anything here
4045                         // return (Math.round(x*100) / 100).toLocaleString();
4046                         // return state.legendFormatValue(x);
4047                         return x;
4048                     }
4049                 }
4050             },
4051             legendFormatter: function(data) {
4052                 var elements = state.element_legend_childs;
4053
4054                 // if the hidden div is not there
4055                 // we are not managing the legend
4056                 if(elements.hidden === null) return;
4057
4058                 if (typeof data.x !== 'undefined') {
4059                     state.legendSetDate(data.x);
4060                     var i = data.series.length;
4061                     while(i--) {
4062                         var series = data.series[i];
4063                         if(!series.isVisible) continue;
4064                         state.legendSetLabelValue(series.label, series.y);
4065                     }
4066                 }
4067
4068                 return '';
4069             },
4070             drawCallback: function(dygraph, is_initial) {
4071                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4072                     state.dygraph_user_action = false;
4073
4074                     var x_range = dygraph.xAxisRange();
4075                     var after = Math.round(x_range[0]);
4076                     var before = Math.round(x_range[1]);
4077
4078                     if(NETDATA.options.debug.dygraph === true)
4079                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4080
4081                     if(before <= state.netdata_last && after >= state.netdata_first)
4082                         state.updateChartPanOrZoom(after, before);
4083                 }
4084             },
4085             zoomCallback: function(minDate, maxDate, yRanges) {
4086                 if(NETDATA.options.debug.dygraph === true)
4087                     state.log('dygraphZoomCallback()');
4088
4089                 state.globalSelectionSyncStop();
4090                 state.globalSelectionSyncDelay();
4091                 state.setMode('zoom');
4092
4093                 // refresh it to the greatest possible zoom level
4094                 state.dygraph_user_action = true;
4095                 state.dygraph_force_zoom = true;
4096                 state.updateChartPanOrZoom(minDate, maxDate);
4097             },
4098             highlightCallback: function(event, x, points, row, seriesName) {
4099                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4100                     state.log('dygraphHighlightCallback()');
4101
4102                 state.pauseChart();
4103
4104                 // there is a bug in dygraph when the chart is zoomed enough
4105                 // the time it thinks is selected is wrong
4106                 // here we calculate the time t based on the row number selected
4107                 // which is ok
4108                 var t = state.data_after + row * state.data_update_every;
4109                 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
4110
4111                 state.globalSelectionSync(x);
4112
4113                 // fix legend zIndex using the internal structures of dygraph legend module
4114                 // this works, but it is a hack!
4115                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4116             },
4117             unhighlightCallback: function(event) {
4118                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4119                     state.log('dygraphUnhighlightCallback()');
4120
4121                 state.unpauseChart();
4122                 state.globalSelectionSyncStop();
4123             },
4124             interactionModel : {
4125                 mousedown: function(event, dygraph, context) {
4126                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4127                         state.log('interactionModel.mousedown()');
4128
4129                     state.dygraph_user_action = true;
4130                     state.globalSelectionSyncStop();
4131
4132                     if(NETDATA.options.debug.dygraph === true)
4133                         state.log('dygraphMouseDown()');
4134
4135                     // Right-click should not initiate a zoom.
4136                     if(event.button && event.button === 2) return;
4137
4138                     context.initializeMouseDown(event, dygraph, context);
4139
4140                     if(event.button && event.button === 1) {
4141                         if (event.altKey || event.shiftKey) {
4142                             state.setMode('pan');
4143                             state.globalSelectionSyncDelay();
4144                             Dygraph.startPan(event, dygraph, context);
4145                         }
4146                         else {
4147                             state.setMode('zoom');
4148                             state.globalSelectionSyncDelay();
4149                             Dygraph.startZoom(event, dygraph, context);
4150                         }
4151                     }
4152                     else {
4153                         if (event.altKey || event.shiftKey) {
4154                             state.setMode('zoom');
4155                             state.globalSelectionSyncDelay();
4156                             Dygraph.startZoom(event, dygraph, context);
4157                         }
4158                         else {
4159                             state.setMode('pan');
4160                             state.globalSelectionSyncDelay();
4161                             Dygraph.startPan(event, dygraph, context);
4162                         }
4163                     }
4164                 },
4165                 mousemove: function(event, dygraph, context) {
4166                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4167                         state.log('interactionModel.mousemove()');
4168
4169                     if(context.isPanning) {
4170                         state.dygraph_user_action = true;
4171                         state.globalSelectionSyncStop();
4172                         state.globalSelectionSyncDelay();
4173                         state.setMode('pan');
4174                         Dygraph.movePan(event, dygraph, context);
4175                     }
4176                     else if(context.isZooming) {
4177                         state.dygraph_user_action = true;
4178                         state.globalSelectionSyncStop();
4179                         state.globalSelectionSyncDelay();
4180                         state.setMode('zoom');
4181                         Dygraph.moveZoom(event, dygraph, context);
4182                     }
4183                 },
4184                 mouseup: function(event, dygraph, context) {
4185                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4186                         state.log('interactionModel.mouseup()');
4187
4188                     if (context.isPanning) {
4189                         state.dygraph_user_action = true;
4190                         state.globalSelectionSyncDelay();
4191                         Dygraph.endPan(event, dygraph, context);
4192                     }
4193                     else if (context.isZooming) {
4194                         state.dygraph_user_action = true;
4195                         state.globalSelectionSyncDelay();
4196                         Dygraph.endZoom(event, dygraph, context);
4197                     }
4198                 },
4199                 click: function(event, dygraph, context) {
4200                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4201                         state.log('interactionModel.click()');
4202
4203                     event.preventDefault();
4204                 },
4205                 dblclick: function(event, dygraph, context) {
4206                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4207                         state.log('interactionModel.dblclick()');
4208                     NETDATA.resetAllCharts(state);
4209                 },
4210                 mousewheel: function(event, dygraph, context) {
4211                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4212                         state.log('interactionModel.mousewheel()');
4213
4214                     // Take the offset of a mouse event on the dygraph canvas and
4215                     // convert it to a pair of percentages from the bottom left.
4216                     // (Not top left, bottom is where the lower value is.)
4217                     function offsetToPercentage(g, offsetX, offsetY) {
4218                         // This is calculating the pixel offset of the leftmost date.
4219                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4220                         var yar0 = g.yAxisRange(0);
4221
4222                         // This is calculating the pixel of the higest value. (Top pixel)
4223                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4224
4225                         // x y w and h are relative to the corner of the drawing area,
4226                         // so that the upper corner of the drawing area is (0, 0).
4227                         var x = offsetX - xOffset;
4228                         var y = offsetY - yOffset;
4229
4230                         // This is computing the rightmost pixel, effectively defining the
4231                         // width.
4232                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4233
4234                         // This is computing the lowest pixel, effectively defining the height.
4235                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4236
4237                         // Percentage from the left.
4238                         var xPct = w === 0 ? 0 : (x / w);
4239                         // Percentage from the top.
4240                         var yPct = h === 0 ? 0 : (y / h);
4241
4242                         // The (1-) part below changes it from "% distance down from the top"
4243                         // to "% distance up from the bottom".
4244                         return [xPct, (1-yPct)];
4245                     }
4246
4247                     // Adjusts [x, y] toward each other by zoomInPercentage%
4248                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4249                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4250                     //
4251                     // If a bias is missing it splits it down the middle.
4252                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4253                         xBias = xBias || 0.5;
4254                         yBias = yBias || 0.5;
4255
4256                         function adjustAxis(axis, zoomInPercentage, bias) {
4257                             var delta = axis[1] - axis[0];
4258                             var increment = delta * zoomInPercentage;
4259                             var foo = [increment * bias, increment * (1-bias)];
4260
4261                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4262                         }
4263
4264                         var yAxes = g.yAxisRanges();
4265                         var newYAxes = [];
4266                         for (var i = 0; i < yAxes.length; i++) {
4267                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4268                         }
4269
4270                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4271                     }
4272
4273                     if(event.altKey || event.shiftKey) {
4274                         state.dygraph_user_action = true;
4275
4276                         state.globalSelectionSyncStop();
4277                         state.globalSelectionSyncDelay();
4278
4279                         // http://dygraphs.com/gallery/interaction-api.js
4280                         var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4281                         var percentage = normal / 50;
4282
4283                         if (!(event.offsetX && event.offsetY)){
4284                             event.offsetX = event.layerX - event.target.offsetLeft;
4285                             event.offsetY = event.layerY - event.target.offsetTop;
4286                         }
4287
4288                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4289                         var xPct = percentages[0];
4290                         var yPct = percentages[1];
4291
4292                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4293
4294                         var after = new_x_range[0];
4295                         var before = new_x_range[1];
4296
4297                         var first = state.netdata_first + state.data_update_every;
4298                         var last = state.netdata_last + state.data_update_every;
4299
4300                         if(before > last) {
4301                             after -= (before - last);
4302                             before = last;
4303                         }
4304                         if(after < first) {
4305                             after = first;
4306                         }
4307
4308                         state.setMode('zoom');
4309                         if(state.updateChartPanOrZoom(after, before) === true)
4310                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4311
4312                         event.preventDefault();
4313                     }
4314                 },
4315                 touchstart: function(event, dygraph, context) {
4316                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4317                         state.log('interactionModel.touchstart()');
4318
4319                     state.dygraph_user_action = true;
4320                     state.setMode('zoom');
4321                     state.pauseChart();
4322
4323                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4324
4325                     // we overwrite the touch directions at the end, to overwrite
4326                     // the internal default of dygraphs
4327                     context.touchDirections = { x: true, y: false };
4328
4329                     state.dygraph_last_touch_start = new Date().getTime();
4330                     state.dygraph_last_touch_move = 0;
4331
4332                     if(typeof event.touches[0].pageX === 'number')
4333                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4334                     else
4335                         state.dygraph_last_touch_page_x = 0;
4336                 },
4337                 touchmove: function(event, dygraph, context) {
4338                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4339                         state.log('interactionModel.touchmove()');
4340
4341                     state.dygraph_user_action = true;
4342                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4343
4344                     state.dygraph_last_touch_move = new Date().getTime();
4345                 },
4346                 touchend: function(event, dygraph, context) {
4347                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4348                         state.log('interactionModel.touchend()');
4349
4350                     state.dygraph_user_action = true;
4351                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4352
4353                     // if it didn't move, it is a selection
4354                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4355                         // internal api of dygraphs
4356                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4357                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4358                         if(NETDATA.dygraphSetSelection(state, t) === true)
4359                             state.globalSelectionSync(t);
4360                     }
4361
4362                     // if it was double tap within double click time, reset the charts
4363                     var now = new Date().getTime();
4364                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4365                         if(state.dygraph_last_touch_move === 0) {
4366                             var dt = now - state.dygraph_last_touch_end;
4367                             if(dt <= NETDATA.options.current.double_click_speed)
4368                                 NETDATA.resetAllCharts(state);
4369                         }
4370                     }
4371
4372                     // remember the timestamp of the last touch end
4373                     state.dygraph_last_touch_end = now;
4374                 }
4375             }
4376         };
4377
4378         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4379             state.dygraph_options.drawGrid = false;
4380             state.dygraph_options.drawAxis = false;
4381             state.dygraph_options.title = undefined;
4382             state.dygraph_options.ylabel = undefined;
4383             state.dygraph_options.yLabelWidth = 0;
4384             state.dygraph_options.labelsDivWidth = 120;
4385             state.dygraph_options.labelsDivStyles.width = '120px';
4386             state.dygraph_options.labelsSeparateLines = true;
4387             state.dygraph_options.rightGap = 0;
4388             state.dygraph_options.yRangePad = 1;
4389         }
4390
4391         if(smooth === true) {
4392             state.dygraph_smooth_eligible = true;
4393
4394             if(NETDATA.options.current.smooth_plot === true)
4395                 state.dygraph_options.plotter = smoothPlotter;
4396         }
4397         else state.dygraph_smooth_eligible = false;
4398
4399         state.dygraph_instance = new Dygraph(state.element_chart,
4400             data.result.data, state.dygraph_options);
4401
4402         state.dygraph_force_zoom = false;
4403         state.dygraph_user_action = false;
4404         state.dygraph_last_rendered = new Date().getTime();
4405         return true;
4406     };
4407
4408     // ----------------------------------------------------------------------------------------------------------------
4409     // morris
4410
4411     NETDATA.morrisInitialize = function(callback) {
4412         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4413
4414             // morris requires raphael
4415             if(!NETDATA.chartLibraries.raphael.initialized) {
4416                 if(NETDATA.chartLibraries.raphael.enabled) {
4417                     NETDATA.raphaelInitialize(function() {
4418                         NETDATA.morrisInitialize(callback);
4419                     });
4420                 }
4421                 else {
4422                     NETDATA.chartLibraries.morris.enabled = false;
4423                     if(typeof callback === "function")
4424                         callback();
4425                 }
4426             }
4427             else {
4428                 NETDATA._loadCSS(NETDATA.morris_css);
4429
4430                 $.ajax({
4431                     url: NETDATA.morris_js,
4432                     cache: true,
4433                     dataType: "script",
4434                     xhrFields: { withCredentials: true } // required for the cookie
4435                 })
4436                 .done(function() {
4437                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4438                 })
4439                 .fail(function() {
4440                     NETDATA.chartLibraries.morris.enabled = false;
4441                     NETDATA.error(100, NETDATA.morris_js);
4442                 })
4443                 .always(function() {
4444                     if(typeof callback === "function")
4445                         callback();
4446                 });
4447             }
4448         }
4449         else {
4450             NETDATA.chartLibraries.morris.enabled = false;
4451             if(typeof callback === "function")
4452                 callback();
4453         }
4454     };
4455
4456     NETDATA.morrisChartUpdate = function(state, data) {
4457         state.morris_instance.setData(data.result.data);
4458         return true;
4459     };
4460
4461     NETDATA.morrisChartCreate = function(state, data) {
4462
4463         state.morris_options = {
4464                 element: state.element_chart.id,
4465                 data: data.result.data,
4466                 xkey: 'time',
4467                 ykeys: data.dimension_names,
4468                 labels: data.dimension_names,
4469                 lineWidth: 2,
4470                 pointSize: 3,
4471                 smooth: true,
4472                 hideHover: 'auto',
4473                 parseTime: true,
4474                 continuousLine: false,
4475                 behaveLikeLine: false
4476         };
4477
4478         if(state.chart.chart_type === 'line')
4479             state.morris_instance = new Morris.Line(state.morris_options);
4480
4481         else if(state.chart.chart_type === 'area') {
4482             state.morris_options.behaveLikeLine = true;
4483             state.morris_instance = new Morris.Area(state.morris_options);
4484         }
4485         else // stacked
4486             state.morris_instance = new Morris.Area(state.morris_options);
4487
4488         return true;
4489     };
4490
4491     // ----------------------------------------------------------------------------------------------------------------
4492     // raphael
4493
4494     NETDATA.raphaelInitialize = function(callback) {
4495         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4496             $.ajax({
4497                 url: NETDATA.raphael_js,
4498                 cache: true,
4499                 dataType: "script",
4500                 xhrFields: { withCredentials: true } // required for the cookie
4501             })
4502             .done(function() {
4503                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4504             })
4505             .fail(function() {
4506                 NETDATA.chartLibraries.raphael.enabled = false;
4507                 NETDATA.error(100, NETDATA.raphael_js);
4508             })
4509             .always(function() {
4510                 if(typeof callback === "function")
4511                     callback();
4512             });
4513         }
4514         else {
4515             NETDATA.chartLibraries.raphael.enabled = false;
4516             if(typeof callback === "function")
4517                 callback();
4518         }
4519     };
4520
4521     NETDATA.raphaelChartUpdate = function(state, data) {
4522         $(state.element_chart).raphael(data.result, {
4523             width: state.chartWidth(),
4524             height: state.chartHeight()
4525         });
4526
4527         return false;
4528     };
4529
4530     NETDATA.raphaelChartCreate = function(state, data) {
4531         $(state.element_chart).raphael(data.result, {
4532             width: state.chartWidth(),
4533             height: state.chartHeight()
4534         });
4535
4536         return false;
4537     };
4538
4539     // ----------------------------------------------------------------------------------------------------------------
4540     // C3
4541
4542     NETDATA.c3Initialize = function(callback) {
4543         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4544
4545             // C3 requires D3
4546             if(!NETDATA.chartLibraries.d3.initialized) {
4547                 if(NETDATA.chartLibraries.d3.enabled) {
4548                     NETDATA.d3Initialize(function() {
4549                         NETDATA.c3Initialize(callback);
4550                     });
4551                 }
4552                 else {
4553                     NETDATA.chartLibraries.c3.enabled = false;
4554                     if(typeof callback === "function")
4555                         callback();
4556                 }
4557             }
4558             else {
4559                 NETDATA._loadCSS(NETDATA.c3_css);
4560
4561                 $.ajax({
4562                     url: NETDATA.c3_js,
4563                     cache: true,
4564                     dataType: "script",
4565                     xhrFields: { withCredentials: true } // required for the cookie
4566                 })
4567                 .done(function() {
4568                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4569                 })
4570                 .fail(function() {
4571                     NETDATA.chartLibraries.c3.enabled = false;
4572                     NETDATA.error(100, NETDATA.c3_js);
4573                 })
4574                 .always(function() {
4575                     if(typeof callback === "function")
4576                         callback();
4577                 });
4578             }
4579         }
4580         else {
4581             NETDATA.chartLibraries.c3.enabled = false;
4582             if(typeof callback === "function")
4583                 callback();
4584         }
4585     };
4586
4587     NETDATA.c3ChartUpdate = function(state, data) {
4588         state.c3_instance.destroy();
4589         return NETDATA.c3ChartCreate(state, data);
4590
4591         //state.c3_instance.load({
4592         //  rows: data.result,
4593         //  unload: true
4594         //});
4595
4596         //return true;
4597     };
4598
4599     NETDATA.c3ChartCreate = function(state, data) {
4600
4601         state.element_chart.id = 'c3-' + state.uuid;
4602         // console.log('id = ' + state.element_chart.id);
4603
4604         state.c3_instance = c3.generate({
4605             bindto: '#' + state.element_chart.id,
4606             size: {
4607                 width: state.chartWidth(),
4608                 height: state.chartHeight()
4609             },
4610             color: {
4611                 pattern: state.chartColors()
4612             },
4613             data: {
4614                 x: 'time',
4615                 rows: data.result,
4616                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4617             },
4618             axis: {
4619                 x: {
4620                     type: 'timeseries',
4621                     tick: {
4622                         format: function(x) {
4623                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4624                         }
4625                     }
4626                 }
4627             },
4628             grid: {
4629                 x: {
4630                     show: true
4631                 },
4632                 y: {
4633                     show: true
4634                 }
4635             },
4636             point: {
4637                 show: false
4638             },
4639             line: {
4640                 connectNull: false
4641             },
4642             transition: {
4643                 duration: 0
4644             },
4645             interaction: {
4646                 enabled: true
4647             }
4648         });
4649
4650         // console.log(state.c3_instance);
4651
4652         return true;
4653     };
4654
4655     // ----------------------------------------------------------------------------------------------------------------
4656     // D3
4657
4658     NETDATA.d3Initialize = function(callback) {
4659         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4660             $.ajax({
4661                 url: NETDATA.d3_js,
4662                 cache: true,
4663                 dataType: "script",
4664                 xhrFields: { withCredentials: true } // required for the cookie
4665             })
4666             .done(function() {
4667                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4668             })
4669             .fail(function() {
4670                 NETDATA.chartLibraries.d3.enabled = false;
4671                 NETDATA.error(100, NETDATA.d3_js);
4672             })
4673             .always(function() {
4674                 if(typeof callback === "function")
4675                     callback();
4676             });
4677         }
4678         else {
4679             NETDATA.chartLibraries.d3.enabled = false;
4680             if(typeof callback === "function")
4681                 callback();
4682         }
4683     };
4684
4685     NETDATA.d3ChartUpdate = function(state, data) {
4686         return false;
4687     };
4688
4689     NETDATA.d3ChartCreate = function(state, data) {
4690         return false;
4691     };
4692
4693     // ----------------------------------------------------------------------------------------------------------------
4694     // google charts
4695
4696     NETDATA.googleInitialize = function(callback) {
4697         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4698             $.ajax({
4699                 url: NETDATA.google_js,
4700                 cache: true,
4701                 dataType: "script",
4702                 xhrFields: { withCredentials: true } // required for the cookie
4703             })
4704             .done(function() {
4705                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4706                 google.load('visualization', '1.1', {
4707                     'packages': ['corechart', 'controls'],
4708                     'callback': callback
4709                 });
4710             })
4711             .fail(function() {
4712                 NETDATA.chartLibraries.google.enabled = false;
4713                 NETDATA.error(100, NETDATA.google_js);
4714                 if(typeof callback === "function")
4715                     callback();
4716             });
4717         }
4718         else {
4719             NETDATA.chartLibraries.google.enabled = false;
4720             if(typeof callback === "function")
4721                 callback();
4722         }
4723     };
4724
4725     NETDATA.googleChartUpdate = function(state, data) {
4726         var datatable = new google.visualization.DataTable(data.result);
4727         state.google_instance.draw(datatable, state.google_options);
4728         return true;
4729     };
4730
4731     NETDATA.googleChartCreate = function(state, data) {
4732         var datatable = new google.visualization.DataTable(data.result);
4733
4734         state.google_options = {
4735             colors: state.chartColors(),
4736
4737             // do not set width, height - the chart resizes itself
4738             //width: state.chartWidth(),
4739             //height: state.chartHeight(),
4740             lineWidth: 1,
4741             title: state.title,
4742             fontSize: 11,
4743             hAxis: {
4744             //  title: "Time of Day",
4745             //  format:'HH:mm:ss',
4746                 viewWindowMode: 'maximized',
4747                 slantedText: false,
4748                 format:'HH:mm:ss',
4749                 textStyle: {
4750                     fontSize: 9
4751                 },
4752                 gridlines: {
4753                     color: '#EEE'
4754                 }
4755             },
4756             vAxis: {
4757                 title: state.units,
4758                 viewWindowMode: 'pretty',
4759                 minValue: -0.1,
4760                 maxValue: 0.1,
4761                 direction: 1,
4762                 textStyle: {
4763                     fontSize: 9
4764                 },
4765                 gridlines: {
4766                     color: '#EEE'
4767                 }
4768             },
4769             chartArea: {
4770                 width: '65%',
4771                 height: '80%'
4772             },
4773             focusTarget: 'category',
4774             annotation: {
4775                 '1': {
4776                     style: 'line'
4777                 }
4778             },
4779             pointsVisible: 0,
4780             titlePosition: 'out',
4781             titleTextStyle: {
4782                 fontSize: 11
4783             },
4784             tooltip: {
4785                 isHtml: false,
4786                 ignoreBounds: true,
4787                 textStyle: {
4788                     fontSize: 9
4789                 }
4790             },
4791             curveType: 'function',
4792             areaOpacity: 0.3,
4793             isStacked: false
4794         };
4795
4796         switch(state.chart.chart_type) {
4797             case "area":
4798                 state.google_options.vAxis.viewWindowMode = 'maximized';
4799                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4800                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4801                 break;
4802
4803             case "stacked":
4804                 state.google_options.isStacked = true;
4805                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4806                 state.google_options.vAxis.viewWindowMode = 'maximized';
4807                 state.google_options.vAxis.minValue = null;
4808                 state.google_options.vAxis.maxValue = null;
4809                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4810                 break;
4811
4812             default:
4813             case "line":
4814                 state.google_options.lineWidth = 2;
4815                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4816                 break;
4817         }
4818
4819         state.google_instance.draw(datatable, state.google_options);
4820         return true;
4821     };
4822
4823     // ----------------------------------------------------------------------------------------------------------------
4824
4825     NETDATA.percentFromValueMax = function(value, max) {
4826         if(value === null) value = 0;
4827         if(max < value) max = value;
4828
4829         var pcent = 0;
4830         if(max !== 0) {
4831             pcent = Math.round(value * 100 / max);
4832             if(pcent === 0 && value > 0) pcent = 1;
4833         }
4834
4835         return pcent;
4836     };
4837
4838     // ----------------------------------------------------------------------------------------------------------------
4839     // easy-pie-chart
4840
4841     NETDATA.easypiechartInitialize = function(callback) {
4842         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4843             $.ajax({
4844                 url: NETDATA.easypiechart_js,
4845                 cache: true,
4846                 dataType: "script",
4847                 xhrFields: { withCredentials: true } // required for the cookie
4848             })
4849                 .done(function() {
4850                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4851                 })
4852                 .fail(function() {
4853                     NETDATA.chartLibraries.easypiechart.enabled = false;
4854                     NETDATA.error(100, NETDATA.easypiechart_js);
4855                 })
4856                 .always(function() {
4857                     if(typeof callback === "function")
4858                         callback();
4859                 })
4860         }
4861         else {
4862             NETDATA.chartLibraries.easypiechart.enabled = false;
4863             if(typeof callback === "function")
4864                 callback();
4865         }
4866     };
4867
4868     NETDATA.easypiechartClearSelection = function(state) {
4869         if(typeof state.easyPieChartEvent !== 'undefined') {
4870             if(state.easyPieChartEvent.timer !== null)
4871                 clearTimeout(state.easyPieChartEvent.timer);
4872
4873             state.easyPieChartEvent.timer = null;
4874         }
4875
4876         if(state.isAutoRefreshable() === true && state.data !== null) {
4877             NETDATA.easypiechartChartUpdate(state, state.data);
4878         }
4879         else {
4880             state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4881             state.easyPieChart_instance.update(0);
4882         }
4883         state.easyPieChart_instance.enableAnimation();
4884
4885         return true;
4886     };
4887
4888     NETDATA.easypiechartSetSelection = function(state, t) {
4889         if(state.timeIsVisible(t) !== true)
4890             return NETDATA.easypiechartClearSelection(state);
4891
4892         var slot = state.calculateRowForTime(t);
4893         if(slot < 0 || slot >= state.data.result.length)
4894             return NETDATA.easypiechartClearSelection(state);
4895
4896         if(typeof state.easyPieChartEvent === 'undefined') {
4897             state.easyPieChartEvent = {
4898                 timer: null,
4899                 value: 0,
4900                 pcent: 0
4901             };
4902         }
4903
4904         var value = state.data.result[state.data.result.length - 1 - slot];
4905         var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4906         var pcent = NETDATA.percentFromValueMax(value, max);
4907
4908         state.easyPieChartEvent.value = value;
4909         state.easyPieChartEvent.pcent = pcent;
4910         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4911
4912         if(state.easyPieChartEvent.timer === null) {
4913             state.easyPieChart_instance.disableAnimation();
4914
4915             state.easyPieChartEvent.timer = setTimeout(function() {
4916                 state.easyPieChartEvent.timer = null;
4917                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4918             }, NETDATA.options.current.charts_selection_animation_delay);
4919         }
4920
4921         return true;
4922     };
4923
4924     NETDATA.easypiechartChartUpdate = function(state, data) {
4925         var value, max, pcent;
4926
4927         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4928             value = null;
4929             max = 0;
4930             pcent = 0;
4931         }
4932         else {
4933             value = data.result[0];
4934             max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4935             pcent = NETDATA.percentFromValueMax(value, max);
4936         }
4937
4938         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4939         state.easyPieChart_instance.update(pcent);
4940         return true;
4941     };
4942
4943     NETDATA.easypiechartChartCreate = function(state, data) {
4944         var self = $(state.element);
4945         var chart = $(state.element_chart);
4946
4947         var value = data.result[0];
4948         var max = self.data('easypiechart-max-value') || null;
4949         var adjust = self.data('easypiechart-adjust') || null;
4950
4951         if(max === null) {
4952             max = data.max;
4953             state.easyPieChartMax = null;
4954         }
4955         else
4956             state.easyPieChartMax = max;
4957
4958         var pcent = NETDATA.percentFromValueMax(value, max);
4959
4960         chart.data('data-percent', pcent);
4961
4962         var size;
4963         switch(adjust) {
4964             case 'width': size = state.chartHeight(); break;
4965             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4966             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4967             case 'height':
4968             default: size = state.chartWidth(); break;
4969         }
4970         state.element.style.width = size + 'px';
4971         state.element.style.height = size + 'px';
4972
4973         var stroke = Math.floor(size / 22);
4974         if(stroke < 3) stroke = 2;
4975
4976         var valuefontsize = Math.floor((size * 2 / 3) / 5);
4977         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4978         state.easyPieChartLabel = document.createElement('span');
4979         state.easyPieChartLabel.className = 'easyPieChartLabel';
4980         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4981         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4982         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4983         state.element_chart.appendChild(state.easyPieChartLabel);
4984
4985         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4986         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4987         state.easyPieChartTitle = document.createElement('span');
4988         state.easyPieChartTitle.className = 'easyPieChartTitle';
4989         state.easyPieChartTitle.innerHTML = state.title;
4990         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4991         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4992         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4993         state.element_chart.appendChild(state.easyPieChartTitle);
4994
4995         var unitfontsize = Math.round(titlefontsize * 0.9);
4996         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4997         state.easyPieChartUnits = document.createElement('span');
4998         state.easyPieChartUnits.className = 'easyPieChartUnits';
4999         state.easyPieChartUnits.innerHTML = state.units;
5000         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5001         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5002         state.element_chart.appendChild(state.easyPieChartUnits);
5003
5004         var barColor = self.data('easypiechart-barcolor');
5005         if(typeof barColor === 'undefined' || barColor === null)
5006             barColor = state.chartColors()[0];
5007         else {
5008             // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5009             var tmp = eval(barColor);
5010             if(typeof tmp === 'function')
5011                 barColor = tmp;
5012         }
5013
5014         chart.easyPieChart({
5015             barColor: barColor,
5016             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5017             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5018             scaleLength: self.data('easypiechart-scalelength') || 5,
5019             lineCap: self.data('easypiechart-linecap') || 'round',
5020             lineWidth: self.data('easypiechart-linewidth') || stroke,
5021             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5022             size: self.data('easypiechart-size') || size,
5023             rotate: self.data('easypiechart-rotate') || 0,
5024             animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5025             easing: self.data('easypiechart-easing') || undefined
5026         });
5027
5028         // when we just re-create the chart
5029         // do not animate the first update
5030         var animate = true;
5031         if(typeof state.easyPieChart_instance !== 'undefined')
5032             animate = false;
5033
5034         state.easyPieChart_instance = chart.data('easyPieChart');
5035         if(animate === false) state.easyPieChart_instance.disableAnimation();
5036         state.easyPieChart_instance.update(pcent);
5037         if(animate === false) state.easyPieChart_instance.enableAnimation();
5038         return true;
5039     };
5040
5041     // ----------------------------------------------------------------------------------------------------------------
5042     // gauge.js
5043
5044     NETDATA.gaugeInitialize = function(callback) {
5045         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5046             $.ajax({
5047                 url: NETDATA.gauge_js,
5048                 cache: true,
5049                 dataType: "script",
5050                 xhrFields: { withCredentials: true } // required for the cookie
5051             })
5052                 .done(function() {
5053                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5054                 })
5055                 .fail(function() {
5056                     NETDATA.chartLibraries.gauge.enabled = false;
5057                     NETDATA.error(100, NETDATA.gauge_js);
5058                 })
5059                 .always(function() {
5060                     if(typeof callback === "function")
5061                         callback();
5062                 })
5063         }
5064         else {
5065             NETDATA.chartLibraries.gauge.enabled = false;
5066             if(typeof callback === "function")
5067                 callback();
5068         }
5069     };
5070
5071     NETDATA.gaugeAnimation = function(state, status) {
5072         var speed = 32;
5073
5074         if(typeof status === 'boolean' && status === false)
5075             speed = 1000000000;
5076         else if(typeof status === 'number')
5077             speed = status;
5078
5079         // console.log('gauge speed ' + speed);
5080         state.gauge_instance.animationSpeed = speed;
5081         state.___gaugeOld__.speed = speed;
5082     };
5083
5084     NETDATA.gaugeSet = function(state, value, min, max) {
5085         if(typeof value !== 'number') value = 0;
5086         if(typeof min !== 'number') min = 0;
5087         if(typeof max !== 'number') max = 0;
5088         if(value > max) max = value;
5089         if(value < min) min = value;
5090         if(min > max) {
5091             var t = min;
5092             min = max;
5093             max = t;
5094         }
5095         else if(min == max)
5096             max = min + 1;
5097
5098         // gauge.js has an issue if the needle
5099         // is smaller than min or larger than max
5100         // when we set the new values
5101         // the needle will go crazy
5102
5103         // to prevent it, we always feed it
5104         // with a percentage, so that the needle
5105         // is always between min and max
5106         var pcent = (value - min) * 100 / (max - min);
5107
5108         // these should never happen
5109         if(pcent < 0) pcent = 0;
5110         if(pcent > 100) pcent = 100;
5111
5112         state.gauge_instance.set(pcent);
5113         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5114
5115         state.___gaugeOld__.value = value;
5116         state.___gaugeOld__.min = min;
5117         state.___gaugeOld__.max = max;
5118     };
5119
5120     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5121         if(state.___gaugeOld__.valueLabel !== value) {
5122             state.___gaugeOld__.valueLabel = value;
5123             state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5124         }
5125         if(state.___gaugeOld__.minLabel !== min) {
5126             state.___gaugeOld__.minLabel = min;
5127             state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5128         }
5129         if(state.___gaugeOld__.maxLabel !== max) {
5130             state.___gaugeOld__.maxLabel = max;
5131             state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5132         }
5133     };
5134
5135     NETDATA.gaugeClearSelection = function(state) {
5136         if(typeof state.gaugeEvent !== 'undefined') {
5137             if(state.gaugeEvent.timer !== null)
5138                 clearTimeout(state.gaugeEvent.timer);
5139
5140             state.gaugeEvent.timer = null;
5141         }
5142
5143         if(state.isAutoRefreshable() === true && state.data !== null) {
5144             NETDATA.gaugeChartUpdate(state, state.data);
5145         }
5146         else {
5147             NETDATA.gaugeAnimation(state, false);
5148             NETDATA.gaugeSet(state, null, null, null);
5149             NETDATA.gaugeSetLabels(state, null, null, null);
5150         }
5151
5152         NETDATA.gaugeAnimation(state, true);
5153         return true;
5154     };
5155
5156     NETDATA.gaugeSetSelection = function(state, t) {
5157         if(state.timeIsVisible(t) !== true)
5158             return NETDATA.gaugeClearSelection(state);
5159
5160         var slot = state.calculateRowForTime(t);
5161         if(slot < 0 || slot >= state.data.result.length)
5162             return NETDATA.gaugeClearSelection(state);
5163
5164         if(typeof state.gaugeEvent === 'undefined') {
5165             state.gaugeEvent = {
5166                 timer: null,
5167                 value: 0,
5168                 min: 0,
5169                 max: 0
5170             };
5171         }
5172
5173         var value = state.data.result[state.data.result.length - 1 - slot];
5174         var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5175         var min = 0;
5176
5177         state.gaugeEvent.value = value;
5178         state.gaugeEvent.max = max;
5179         state.gaugeEvent.min = min;
5180         NETDATA.gaugeSetLabels(state, value, min, max);
5181
5182         if(state.gaugeEvent.timer === null) {
5183             NETDATA.gaugeAnimation(state, false);
5184
5185             state.gaugeEvent.timer = setTimeout(function() {
5186                 state.gaugeEvent.timer = null;
5187                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5188             }, NETDATA.options.current.charts_selection_animation_delay);
5189         }
5190
5191         return true;
5192     };
5193
5194     NETDATA.gaugeChartUpdate = function(state, data) {
5195         var value, min, max;
5196
5197         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5198             value = 0;
5199             min = 0;
5200             max = 1;
5201             NETDATA.gaugeSetLabels(state, null, null, null);
5202         }
5203         else {
5204             value = data.result[0];
5205             min = 0;
5206             max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5207             if(value > max) max = value;
5208             NETDATA.gaugeSetLabels(state, value, min, max);
5209         }
5210
5211         NETDATA.gaugeSet(state, value, min, max);
5212         return true;
5213     };
5214
5215     NETDATA.gaugeChartCreate = function(state, data) {
5216         var self = $(state.element);
5217         // var chart = $(state.element_chart);
5218
5219         var value = data.result[0];
5220         var max = self.data('gauge-max-value') || null;
5221         var adjust = self.data('gauge-adjust') || null;
5222         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5223         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5224         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5225         var stopColor = self.data('gauge-stop-color') || void 0;
5226         var generateGradient = self.data('gauge-generate-gradient') || false;
5227
5228         if(max === null) {
5229             max = data.max;
5230             state.gaugeMax = null;
5231         }
5232         else
5233             state.gaugeMax = max;
5234
5235         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5236         //switch(adjust) {
5237         //  case 'width': width = height * ratio; break;
5238         //  case 'height':
5239         //  default: height = width / ratio; break;
5240         //}
5241         //state.element.style.width = width.toString() + 'px';
5242         //state.element.style.height = height.toString() + 'px';
5243
5244         var lum_d = 0.05;
5245
5246         var options = {
5247             lines: 12,                  // The number of lines to draw
5248             angle: 0.15,                // The length of each line
5249             lineWidth: 0.44,            // 0.44 The line thickness
5250             pointer: {
5251                 length: 0.8,            // 0.9 The radius of the inner circle
5252                 strokeWidth: 0.035,     // The rotation offset
5253                 color: pointerColor     // Fill color
5254             },
5255             colorStart: startColor,     // Colors
5256             colorStop: stopColor,       // just experiment with them
5257             strokeColor: strokeColor,   // to see which ones work best for you
5258             limitMax: true,
5259             generateGradient: (generateGradient === true)?true:false,
5260             gradientType: 0
5261         };
5262
5263         if (generateGradient.constructor === Array) {
5264             // example options:
5265             // data-gauge-generate-gradient="[0, 50, 100]"
5266             // data-gauge-gradient-percent-color-0="#FFFFFF"
5267             // data-gauge-gradient-percent-color-50="#999900"
5268             // data-gauge-gradient-percent-color-100="#000000"
5269
5270             options.percentColors = new Array();
5271             var len = generateGradient.length;
5272             while(len--) {
5273                 var pcent = generateGradient[len];
5274                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5275                 if(color !== false) {
5276                     var a = new Array();
5277                     a[0] = pcent / 100;
5278                     a[1] = color;
5279                     options.percentColors.unshift(a);
5280                 }
5281             }
5282             if(options.percentColors.length === 0)
5283                 delete options.percentColors;
5284         }
5285         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5286             options.percentColors = [
5287                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5288                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5289                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5290                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5291                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5292                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5293                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5294                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5295                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5296                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5297                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5298         }
5299
5300         state.gauge_canvas = document.createElement('canvas');
5301         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5302         state.gauge_canvas.className = 'gaugeChart';
5303         state.gauge_canvas.width  = width;
5304         state.gauge_canvas.height = height;
5305         state.element_chart.appendChild(state.gauge_canvas);
5306
5307         var valuefontsize = Math.floor(height / 6);
5308         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5309         state.gaugeChartLabel = document.createElement('span');
5310         state.gaugeChartLabel.className = 'gaugeChartLabel';
5311         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5312         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5313         state.element_chart.appendChild(state.gaugeChartLabel);
5314
5315         var titlefontsize = Math.round(valuefontsize / 2);
5316         var titletop = 0;
5317         state.gaugeChartTitle = document.createElement('span');
5318         state.gaugeChartTitle.className = 'gaugeChartTitle';
5319         state.gaugeChartTitle.innerHTML = state.title;
5320         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5321         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5322         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5323         state.element_chart.appendChild(state.gaugeChartTitle);
5324
5325         var unitfontsize = Math.round(titlefontsize * 0.9);
5326         state.gaugeChartUnits = document.createElement('span');
5327         state.gaugeChartUnits.className = 'gaugeChartUnits';
5328         state.gaugeChartUnits.innerHTML = state.units;
5329         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5330         state.element_chart.appendChild(state.gaugeChartUnits);
5331
5332         state.gaugeChartMin = document.createElement('span');
5333         state.gaugeChartMin.className = 'gaugeChartMin';
5334         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5335         state.element_chart.appendChild(state.gaugeChartMin);
5336
5337         state.gaugeChartMax = document.createElement('span');
5338         state.gaugeChartMax.className = 'gaugeChartMax';
5339         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5340         state.element_chart.appendChild(state.gaugeChartMax);
5341
5342         // when we just re-create the chart
5343         // do not animate the first update
5344         var animate = true;
5345         if(typeof state.gauge_instance !== 'undefined')
5346             animate = false;
5347
5348         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5349
5350         state.___gaugeOld__ = {
5351             value: value,
5352             min: 0,
5353             max: max,
5354             valueLabel: null,
5355             minLabel: null,
5356             maxLabel: null
5357         };
5358
5359         // we will always feed a percentage
5360         state.gauge_instance.minValue = 0;
5361         state.gauge_instance.maxValue = 100;
5362
5363         NETDATA.gaugeAnimation(state, animate);
5364         NETDATA.gaugeSet(state, value, 0, max);
5365         NETDATA.gaugeSetLabels(state, value, 0, max);
5366         NETDATA.gaugeAnimation(state, true);
5367         return true;
5368     };
5369
5370     // ----------------------------------------------------------------------------------------------------------------
5371     // Charts Libraries Registration
5372
5373     NETDATA.chartLibraries = {
5374         "dygraph": {
5375             initialize: NETDATA.dygraphInitialize,
5376             create: NETDATA.dygraphChartCreate,
5377             update: NETDATA.dygraphChartUpdate,
5378             resize: function(state) {
5379                 if(typeof state.dygraph_instance.resize === 'function')
5380                     state.dygraph_instance.resize();
5381             },
5382             setSelection: NETDATA.dygraphSetSelection,
5383             clearSelection:  NETDATA.dygraphClearSelection,
5384             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5385             initialized: false,
5386             enabled: true,
5387             format: function(state) { return 'json'; },
5388             options: function(state) { return 'ms|flip'; },
5389             legend: function(state) {
5390                 if(this.isSparkline(state) === false)
5391                     return 'right-side';
5392                 else
5393                     return null;
5394             },
5395             autoresize: function(state) { return true; },
5396             max_updates_to_recreate: function(state) { return 5000; },
5397             track_colors: function(state) { return true; },
5398             pixels_per_point: function(state) {
5399                 if(this.isSparkline(state) === false)
5400                     return 3;
5401                 else
5402                     return 2;
5403             },
5404
5405             isSparkline: function(state) {
5406                 if(typeof state.dygraph_sparkline === 'undefined') {
5407                     var t = $(state.element).data('dygraph-theme');
5408                     if(t === 'sparkline')
5409                         state.dygraph_sparkline = true;
5410                     else
5411                         state.dygraph_sparkline = false;
5412                 }
5413                 return state.dygraph_sparkline;
5414             }
5415         },
5416         "sparkline": {
5417             initialize: NETDATA.sparklineInitialize,
5418             create: NETDATA.sparklineChartCreate,
5419             update: NETDATA.sparklineChartUpdate,
5420             resize: null,
5421             setSelection: undefined, // function(state, t) { return true; },
5422             clearSelection: undefined, // function(state) { return true; },
5423             toolboxPanAndZoom: null,
5424             initialized: false,
5425             enabled: true,
5426             format: function(state) { return 'array'; },
5427             options: function(state) { return 'flip|abs'; },
5428             legend: function(state) { return null; },
5429             autoresize: function(state) { return false; },
5430             max_updates_to_recreate: function(state) { return 5000; },
5431             track_colors: function(state) { return false; },
5432             pixels_per_point: function(state) { return 3; }
5433         },
5434         "peity": {
5435             initialize: NETDATA.peityInitialize,
5436             create: NETDATA.peityChartCreate,
5437             update: NETDATA.peityChartUpdate,
5438             resize: null,
5439             setSelection: undefined, // function(state, t) { return true; },
5440             clearSelection: undefined, // function(state) { return true; },
5441             toolboxPanAndZoom: null,
5442             initialized: false,
5443             enabled: true,
5444             format: function(state) { return 'ssvcomma'; },
5445             options: function(state) { return 'null2zero|flip|abs'; },
5446             legend: function(state) { return null; },
5447             autoresize: function(state) { return false; },
5448             max_updates_to_recreate: function(state) { return 5000; },
5449             track_colors: function(state) { return false; },
5450             pixels_per_point: function(state) { return 3; }
5451         },
5452         "morris": {
5453             initialize: NETDATA.morrisInitialize,
5454             create: NETDATA.morrisChartCreate,
5455             update: NETDATA.morrisChartUpdate,
5456             resize: null,
5457             setSelection: undefined, // function(state, t) { return true; },
5458             clearSelection: undefined, // function(state) { return true; },
5459             toolboxPanAndZoom: null,
5460             initialized: false,
5461             enabled: true,
5462             format: function(state) { return 'json'; },
5463             options: function(state) { return 'objectrows|ms'; },
5464             legend: function(state) { return null; },
5465             autoresize: function(state) { return false; },
5466             max_updates_to_recreate: function(state) { return 50; },
5467             track_colors: function(state) { return false; },
5468             pixels_per_point: function(state) { return 15; }
5469         },
5470         "google": {
5471             initialize: NETDATA.googleInitialize,
5472             create: NETDATA.googleChartCreate,
5473             update: NETDATA.googleChartUpdate,
5474             resize: null,
5475             setSelection: undefined, //function(state, t) { return true; },
5476             clearSelection: undefined, //function(state) { return true; },
5477             toolboxPanAndZoom: null,
5478             initialized: false,
5479             enabled: true,
5480             format: function(state) { return 'datatable'; },
5481             options: function(state) { return ''; },
5482             legend: function(state) { return null; },
5483             autoresize: function(state) { return false; },
5484             max_updates_to_recreate: function(state) { return 300; },
5485             track_colors: function(state) { return false; },
5486             pixels_per_point: function(state) { return 4; }
5487         },
5488         "raphael": {
5489             initialize: NETDATA.raphaelInitialize,
5490             create: NETDATA.raphaelChartCreate,
5491             update: NETDATA.raphaelChartUpdate,
5492             resize: null,
5493             setSelection: undefined, // function(state, t) { return true; },
5494             clearSelection: undefined, // function(state) { return true; },
5495             toolboxPanAndZoom: null,
5496             initialized: false,
5497             enabled: true,
5498             format: function(state) { return 'json'; },
5499             options: function(state) { return ''; },
5500             legend: function(state) { return null; },
5501             autoresize: function(state) { return false; },
5502             max_updates_to_recreate: function(state) { return 5000; },
5503             track_colors: function(state) { return false; },
5504             pixels_per_point: function(state) { return 3; }
5505         },
5506         "c3": {
5507             initialize: NETDATA.c3Initialize,
5508             create: NETDATA.c3ChartCreate,
5509             update: NETDATA.c3ChartUpdate,
5510             resize: null,
5511             setSelection: undefined, // function(state, t) { return true; },
5512             clearSelection: undefined, // function(state) { return true; },
5513             toolboxPanAndZoom: null,
5514             initialized: false,
5515             enabled: true,
5516             format: function(state) { return 'csvjsonarray'; },
5517             options: function(state) { return 'milliseconds'; },
5518             legend: function(state) { return null; },
5519             autoresize: function(state) { return false; },
5520             max_updates_to_recreate: function(state) { return 5000; },
5521             track_colors: function(state) { return false; },
5522             pixels_per_point: function(state) { return 15; }
5523         },
5524         "d3": {
5525             initialize: NETDATA.d3Initialize,
5526             create: NETDATA.d3ChartCreate,
5527             update: NETDATA.d3ChartUpdate,
5528             resize: null,
5529             setSelection: undefined, // function(state, t) { return true; },
5530             clearSelection: undefined, // function(state) { return true; },
5531             toolboxPanAndZoom: null,
5532             initialized: false,
5533             enabled: true,
5534             format: function(state) { return 'json'; },
5535             options: function(state) { return ''; },
5536             legend: function(state) { return null; },
5537             autoresize: function(state) { return false; },
5538             max_updates_to_recreate: function(state) { return 5000; },
5539             track_colors: function(state) { return false; },
5540             pixels_per_point: function(state) { return 3; }
5541         },
5542         "easypiechart": {
5543             initialize: NETDATA.easypiechartInitialize,
5544             create: NETDATA.easypiechartChartCreate,
5545             update: NETDATA.easypiechartChartUpdate,
5546             resize: null,
5547             setSelection: NETDATA.easypiechartSetSelection,
5548             clearSelection: NETDATA.easypiechartClearSelection,
5549             toolboxPanAndZoom: null,
5550             initialized: false,
5551             enabled: true,
5552             format: function(state) { return 'array'; },
5553             options: function(state) { return 'absolute'; },
5554             legend: function(state) { return null; },
5555             autoresize: function(state) { return false; },
5556             max_updates_to_recreate: function(state) { return 5000; },
5557             track_colors: function(state) { return true; },
5558             pixels_per_point: function(state) { return 3; },
5559             aspect_ratio: 100
5560         },
5561         "gauge": {
5562             initialize: NETDATA.gaugeInitialize,
5563             create: NETDATA.gaugeChartCreate,
5564             update: NETDATA.gaugeChartUpdate,
5565             resize: null,
5566             setSelection: NETDATA.gaugeSetSelection,
5567             clearSelection: NETDATA.gaugeClearSelection,
5568             toolboxPanAndZoom: null,
5569             initialized: false,
5570             enabled: true,
5571             format: function(state) { return 'array'; },
5572             options: function(state) { return 'absolute'; },
5573             legend: function(state) { return null; },
5574             autoresize: function(state) { return false; },
5575             max_updates_to_recreate: function(state) { return 5000; },
5576             track_colors: function(state) { return true; },
5577             pixels_per_point: function(state) { return 3; },
5578             aspect_ratio: 70
5579         }
5580     };
5581
5582     NETDATA.registerChartLibrary = function(library, url) {
5583         if(NETDATA.options.debug.libraries === true)
5584             console.log("registering chart library: " + library);
5585
5586         NETDATA.chartLibraries[library].url = url;
5587         NETDATA.chartLibraries[library].initialized = true;
5588         NETDATA.chartLibraries[library].enabled = true;
5589     };
5590
5591     // ----------------------------------------------------------------------------------------------------------------
5592     // Load required JS libraries and CSS
5593
5594     NETDATA.requiredJs = [
5595         {
5596             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5597             async: false,
5598             isAlreadyLoaded: function() {
5599                 // check if bootstrap is loaded
5600                 if(typeof $().emulateTransitionEnd == 'function')
5601                     return true;
5602                 else {
5603                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5604                         return true;
5605                     else
5606                         return false;
5607                 }
5608             }
5609         },
5610         {
5611             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5612             isAlreadyLoaded: function() { return false; }
5613         }
5614     ];
5615
5616     NETDATA.requiredCSS = [
5617         {
5618             url: NETDATA.themes.current.bootstrap_css,
5619             isAlreadyLoaded: function() {
5620                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5621                     return true;
5622                 else
5623                     return false;
5624             }
5625         },
5626         {
5627             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5628             isAlreadyLoaded: function() { return false; }
5629         },
5630         {
5631             url: NETDATA.themes.current.dashboard_css,
5632             isAlreadyLoaded: function() { return false; }
5633         }
5634     ];
5635
5636     NETDATA.loadedRequiredJs = 0;
5637     NETDATA.loadRequiredJs = function(index, callback) {
5638         if(index >= NETDATA.requiredJs.length) {
5639             if(typeof callback === 'function')
5640                 callback();
5641             return;
5642         }
5643
5644         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5645             NETDATA.loadedRequiredJs++;
5646             NETDATA.loadRequiredJs(++index, callback);
5647             return;
5648         }
5649
5650         if(NETDATA.options.debug.main_loop === true)
5651             console.log('loading ' + NETDATA.requiredJs[index].url);
5652
5653         var async = true;
5654         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5655             async = false;
5656
5657         $.ajax({
5658             url: NETDATA.requiredJs[index].url,
5659             cache: true,
5660             dataType: "script",
5661             xhrFields: { withCredentials: true } // required for the cookie
5662         })
5663         .done(function() {
5664             if(NETDATA.options.debug.main_loop === true)
5665                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5666         })
5667         .fail(function() {
5668             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5669         })
5670         .always(function() {
5671             NETDATA.loadedRequiredJs++;
5672
5673             if(async === false)
5674                 NETDATA.loadRequiredJs(++index, callback);
5675         })
5676
5677         if(async === true)
5678             NETDATA.loadRequiredJs(++index, callback);
5679     };
5680
5681     NETDATA.loadRequiredCSS = function(index) {
5682         if(index >= NETDATA.requiredCSS.length)
5683             return;
5684
5685         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5686             NETDATA.loadRequiredCSS(++index);
5687             return;
5688         }
5689
5690         if(NETDATA.options.debug.main_loop === true)
5691             console.log('loading ' + NETDATA.requiredCSS[index].url);
5692
5693         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5694         NETDATA.loadRequiredCSS(++index);
5695     };
5696
5697
5698     // ----------------------------------------------------------------------------------------------------------------
5699     // Registry of netdata hosts
5700
5701     NETDATA.alarms = {
5702         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5703         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5704         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5705         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5706
5707         ms_penalty: 0,                  // the time penalty of the next alarm
5708         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5709                                         // if alarms are shown faster than: one per 500ms
5710
5711         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5712         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5713         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5714                                         // this is used to prevent CLEAR notifications for past events
5715         // notifications_shown: new Array(),
5716
5717         server: null,                   // the server to connect to for fetching alarms
5718         current: null,                  // the list of raised alarms - updated in the background
5719         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
5720
5721         notify: function(entry) {
5722             // console.log('alarm ' + entry.unique_id);
5723
5724             if(entry.updated === true) {
5725                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5726                 return;
5727             }
5728
5729             var value = entry.value;
5730             if(NETDATA.alarms.current !== null) {
5731                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5732                 if(typeof t !== 'undefined' && entry.status == t.status)
5733                     value = t.value;
5734             }
5735
5736             var name = entry.name.replace(/_/g, ' ');
5737             var status = entry.status.toLowerCase();
5738             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5739             var tag = entry.alarm_id;
5740             var icon = 'images/seo-performance-128.png';
5741             var interaction = false;
5742             var data = entry;
5743             var show = true;
5744
5745             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
5746
5747             switch(entry.status) {
5748                 case 'REMOVED':
5749                     show = false;
5750                     break;
5751
5752                 case 'UNDEFINED':
5753                     return;
5754
5755                 case 'UNINITIALIZED':
5756                     return;
5757
5758                 case 'CLEAR':
5759                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5760                         // console.log('alarm ' + entry.unique_id + ' is not current');
5761                         return;
5762                     }
5763                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5764                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5765                         return;
5766                     }
5767                     title = name + ' back to normal';
5768                     icon = 'images/check-mark-2-128-green.png'
5769                     interaction = false;
5770                     break;
5771
5772                 case 'WARNING':
5773                     if(entry.old_status === 'CRITICAL')
5774                         status = 'demoted to ' + entry.status.toLowerCase();
5775
5776                     icon = 'images/alert-128-orange.png';
5777                     interaction = false;
5778                     break;
5779
5780                 case 'CRITICAL':
5781                     if(entry.old_status === 'WARNING')
5782                         status = 'escalated to ' + entry.status.toLowerCase();
5783                     
5784                     icon = 'images/alert-128-red.png'
5785                     interaction = true;
5786                     break;
5787
5788                 default:
5789                     console.log('invalid alarm status ' + entry.status);
5790                     return;
5791             }
5792
5793             /*
5794             // cleanup old notifications with the same alarm_id as this one
5795             // FIXME: it does not seem to work on any web browser!
5796             var len = NETDATA.alarms.notifications_shown.length;
5797             while(len--) {
5798                 var n = NETDATA.alarms.notifications_shown[len];
5799                 if(n.data.alarm_id === entry.alarm_id) {
5800                     console.log('removing old alarm ' + n.data.unique_id);
5801
5802                     // close the notification
5803                     n.close.bind(n);
5804
5805                     // remove it from the array
5806                     NETDATA.alarms.notifications_shown.splice(len, 1);
5807                     len = NETDATA.alarms.notifications_shown.length;
5808                 }
5809             }
5810             */
5811
5812             if(show === true) {
5813
5814                 setTimeout(function() {
5815                     // show this notification
5816                     // console.log('new notification: ' + title);
5817                     var n = new Notification(title, {
5818                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5819                         tag: tag,
5820                         requireInteraction: interaction,
5821                         icon: NETDATA.serverDefault + icon,
5822                         data: data
5823                     });
5824
5825                     n.onclick = function(event) {
5826                         event.preventDefault();
5827                         NETDATA.alarms.onclick(event.target.data);
5828                     };
5829
5830                     // console.log(n);
5831                     // NETDATA.alarms.notifications_shown.push(n);
5832                     // console.log(entry);
5833                 }, NETDATA.alarms.ms_penalty);
5834
5835                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5836             }
5837         },
5838
5839         scrollToChart: function(chart_id) {
5840             if(typeof chart_id === 'string') {
5841                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5842                 if(typeof offset !== 'undefined') {
5843                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5844                     return true;
5845                 }
5846             }
5847             return false;
5848         },
5849
5850         scrollToAlarm: function(alarm) {
5851             if(typeof alarm === 'object') {
5852                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5853
5854                 if(ret === true && NETDATA.options.page_is_visible === false)
5855                     window.focus();
5856                 //    alert('netdata dashboard will now scroll to chart: ' + alarm.chart + '\n\nThis alarm opened to bring the browser window in front of the screen. Click on the dashboard to prevent it from appearing again.');
5857             }
5858
5859         },
5860
5861         notifyAll: function() {
5862             // console.log('FETCHING ALARM LOG');
5863             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5864                 // console.log('ALARM LOG FETCHED');
5865
5866                 if(data === null || typeof data !== 'object') {
5867                     console.log('invalid alarms log response');
5868                     return;
5869                 }
5870
5871                 if(data.length === 0) {
5872                     console.log('received empty alarm log');
5873                     return;
5874                 }
5875
5876                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5877
5878                 data.sort(function(a, b) {
5879                     if(a.unique_id > b.unique_id) return -1;
5880                     if(a.unique_id < b.unique_id) return 1;
5881                     return 0;
5882                 });
5883
5884                 NETDATA.alarms.ms_penalty = 0;
5885
5886                 var len = data.length;
5887                 while(len--) {
5888                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5889                         NETDATA.alarms.notify(data[len]);
5890                     }
5891                     //else
5892                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5893                 }
5894
5895                 NETDATA.alarms.last_notification_id = data[0].unique_id;
5896                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5897                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5898             })
5899         },
5900
5901         check_notifications: function() {
5902             // returns true if we should fire 1+ notifications
5903
5904             if(NETDATA.alarms.notifications !== true) {
5905                 // console.log('notifications not available');
5906                 return false;
5907             }
5908
5909             if(Notification.permission !== 'granted') {
5910                 // console.log('notifications not granted');
5911                 return false;
5912             }
5913
5914             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5915                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5916
5917                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5918                     // console.log('new alarms detected');
5919                     return true;
5920                 }
5921                 //else console.log('no new alarms');
5922             }
5923             // else console.log('cannot process alarms');
5924
5925             return false;
5926         },
5927
5928         get: function(what, callback) {
5929             $.ajax({
5930                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5931                 async: true,
5932                 cache: false,
5933                 headers: {
5934                     'Cache-Control': 'no-cache, no-store',
5935                     'Pragma': 'no-cache'
5936                 },
5937                 xhrFields: { withCredentials: true } // required for the cookie
5938             })
5939                 .done(function(data) {
5940                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5941                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5942
5943                     if(typeof callback === 'function')
5944                         callback(data);
5945                 })
5946                 .fail(function() {
5947                     NETDATA.error(415, NETDATA.alarms.server);
5948
5949                     if(typeof callback === 'function')
5950                         callback(null);
5951                 });
5952         },
5953
5954         update_forever: function() {
5955             NETDATA.alarms.get('active', function(data) {
5956                 if(data !== null) {
5957                     NETDATA.alarms.current = data;
5958
5959                     if(NETDATA.alarms.check_notifications() === true) {
5960                         NETDATA.alarms.notifyAll();
5961                     }
5962
5963                     if (typeof NETDATA.alarms.callback === 'function') {
5964                         NETDATA.alarms.callback(data);
5965                     }
5966
5967                     // Health monitoring is disabled on this netdata
5968                     if(data.status === false) return;
5969                 }
5970
5971                 setTimeout(NETDATA.alarms.update_forever, 10000);
5972             });
5973         },
5974
5975         get_log: function(last_id, callback) {
5976             // console.log('fetching all log after ' + last_id.toString());
5977             $.ajax({
5978                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5979                 async: true,
5980                 cache: false,
5981                 headers: {
5982                     'Cache-Control': 'no-cache, no-store',
5983                     'Pragma': 'no-cache'
5984                 },
5985                 xhrFields: { withCredentials: true } // required for the cookie
5986             })
5987                 .done(function(data) {
5988                     if(typeof callback === 'function')
5989                         callback(data);
5990                 })
5991                 .fail(function() {
5992                     NETDATA.error(416, NETDATA.alarms.server);
5993
5994                     if(typeof callback === 'function')
5995                         callback(null);
5996                 });
5997         },
5998
5999         init: function() {
6000             var host = NETDATA.serverDefault;
6001             while(host.slice(-1) === '/')
6002                 host = host.substring(0, host.length - 1);
6003             NETDATA.alarms.server = host;
6004
6005             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6006
6007             if(NETDATA.alarms.onclick === null)
6008                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6009
6010             if(netdataShowAlarms === true) {
6011                 NETDATA.alarms.update_forever();
6012             
6013                 if('Notification' in window) {
6014                     // console.log('notifications available');
6015                     NETDATA.alarms.notifications = true;
6016
6017                     if(Notification.permission === 'default')
6018                         Notification.requestPermission();
6019                 }
6020             }
6021         }
6022     };
6023
6024     // ----------------------------------------------------------------------------------------------------------------
6025     // Registry of netdata hosts
6026
6027     NETDATA.registry = {
6028         server: null,       // the netdata registry server
6029         person_guid: null,  // the unique ID of this browser / user
6030         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6031         hostname: null,     // the hostname of the netdata server that served dashboard.js
6032         machines: null,         // the user's other URLs
6033         machines_array: null,   // the user's other URLs in an array
6034         person_urls: null,
6035
6036         parsePersonUrls: function(person_urls) {
6037             // console.log(person_urls);
6038             NETDATA.registry.person_urls = person_urls;
6039
6040             if(person_urls) {
6041                 NETDATA.registry.machines = {};
6042                 NETDATA.registry.machines_array = new Array();
6043
6044                 var now = new Date().getTime();
6045                 var apu = person_urls;
6046                 var i = apu.length;
6047                 while(i--) {
6048                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6049                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6050
6051                         var obj = {
6052                             guid: apu[i][0],
6053                             url: apu[i][1],
6054                             last_t: apu[i][2],
6055                             accesses: apu[i][3],
6056                             name: apu[i][4],
6057                             alternate_urls: new Array()
6058                         };
6059                         obj.alternate_urls.push(apu[i][1]);
6060
6061                         NETDATA.registry.machines[apu[i][0]] = obj;
6062                         NETDATA.registry.machines_array.push(obj);
6063                     }
6064                     else {
6065                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6066
6067                         var pu = NETDATA.registry.machines[apu[i][0]];
6068                         if(pu.last_t < apu[i][2]) {
6069                             pu.url = apu[i][1];
6070                             pu.last_t = apu[i][2];
6071                             pu.name = apu[i][4];
6072                         }
6073                         pu.accesses += apu[i][3];
6074                         pu.alternate_urls.push(apu[i][1]);
6075                     }
6076                 }
6077             }
6078
6079             if(typeof netdataRegistryCallback === 'function')
6080                 netdataRegistryCallback(NETDATA.registry.machines_array);
6081         },
6082
6083         init: function() {
6084             if(netdataRegistry !== true) return;
6085
6086             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6087                 if(data) {
6088                     NETDATA.registry.server = data.registry;
6089                     NETDATA.registry.machine_guid = data.machine_guid;
6090                     NETDATA.registry.hostname = data.hostname;
6091
6092                     NETDATA.registry.access(2, function (person_urls) {
6093                         NETDATA.registry.parsePersonUrls(person_urls);
6094
6095                     });
6096                 }
6097             });
6098         },
6099
6100         hello: function(host, callback) {
6101             while(host.slice(-1) === '/')
6102                 host = host.substring(0, host.length - 1);
6103
6104             // send HELLO to a netdata server:
6105             // 1. verifies the server is reachable
6106             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6107             $.ajax({
6108                     url: host + '/api/v1/registry?action=hello',
6109                     async: true,
6110                     cache: false,
6111                     headers: {
6112                         'Cache-Control': 'no-cache, no-store',
6113                         'Pragma': 'no-cache'
6114                     },
6115                     xhrFields: { withCredentials: true } // required for the cookie
6116                 })
6117                 .done(function(data) {
6118                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6119                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6120                         data = null;
6121                     }
6122
6123                     if(typeof callback === 'function')
6124                         callback(data);
6125                 })
6126                 .fail(function() {
6127                     NETDATA.error(407, host);
6128
6129                     if(typeof callback === 'function')
6130                         callback(null);
6131                 });
6132         },
6133
6134         access: function(max_redirects, callback) {
6135             // send ACCESS to a netdata registry:
6136             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6137             // 2. it responds with a list of netdata servers we know
6138             // the registry identifies us using a cookie it sets the first time we access it
6139             // the registry may respond with a redirect URL to send us to another registry
6140             $.ajax({
6141                     url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
6142                     async: true,
6143                     cache: false,
6144                     headers: {
6145                         'Cache-Control': 'no-cache, no-store',
6146                         'Pragma': 'no-cache'
6147                     },
6148                     xhrFields: { withCredentials: true } // required for the cookie
6149                 })
6150                 .done(function(data) {
6151                     var redirect = null;
6152                     if(typeof data.registry === 'string')
6153                         redirect = data.registry;
6154
6155                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6156                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6157                         data = null;
6158                     }
6159
6160                     if(data === null) {
6161                         if(redirect !== null && max_redirects > 0) {
6162                             NETDATA.registry.server = redirect;
6163                             NETDATA.registry.access(max_redirects - 1, callback);
6164                         }
6165                         else {
6166                             if(typeof callback === 'function')
6167                                 callback(null);
6168                         }
6169                     }
6170                     else {
6171                         if(typeof data.person_guid === 'string')
6172                             NETDATA.registry.person_guid = data.person_guid;
6173
6174                         if(typeof callback === 'function')
6175                             callback(data.urls);
6176                     }
6177                 })
6178                 .fail(function() {
6179                     NETDATA.error(410, NETDATA.registry.server);
6180
6181                     if(typeof callback === 'function')
6182                         callback(null);
6183                 });
6184         },
6185
6186         delete: function(delete_url, callback) {
6187             // send DELETE to a netdata registry:
6188             $.ajax({
6189                 url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
6190                 async: true,
6191                 cache: false,
6192                 headers: {
6193                     'Cache-Control': 'no-cache, no-store',
6194                     'Pragma': 'no-cache'
6195                 },
6196                 xhrFields: { withCredentials: true } // required for the cookie
6197             })
6198                 .done(function(data) {
6199                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6200                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6201                         data = null;
6202                     }
6203
6204                     if(typeof callback === 'function')
6205                         callback(data);
6206                 })
6207                 .fail(function() {
6208                     NETDATA.error(412, NETDATA.registry.server);
6209
6210                     if(typeof callback === 'function')
6211                         callback(null);
6212                 });
6213         },
6214
6215         search: function(machine_guid, callback) {
6216             // SEARCH for the URLs of a machine:
6217             $.ajax({
6218                 url: NETDATA.registry.server + '/api/v1/registry?action=search&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&for=' + machine_guid,
6219                 async: true,
6220                 cache: false,
6221                 headers: {
6222                     'Cache-Control': 'no-cache, no-store',
6223                     'Pragma': 'no-cache'
6224                 },
6225                 xhrFields: { withCredentials: true } // required for the cookie
6226             })
6227                 .done(function(data) {
6228                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6229                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6230                         data = null;
6231                     }
6232
6233                     if(typeof callback === 'function')
6234                         callback(data);
6235                 })
6236                 .fail(function() {
6237                     NETDATA.error(418, NETDATA.registry.server);
6238
6239                     if(typeof callback === 'function')
6240                         callback(null);
6241                 });
6242         },
6243
6244         switch: function(new_person_guid, callback) {
6245             // impersonate
6246             $.ajax({
6247                 url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
6248                 async: true,
6249                 cache: false,
6250                 headers: {
6251                     'Cache-Control': 'no-cache, no-store',
6252                     'Pragma': 'no-cache'
6253                 },
6254                 xhrFields: { withCredentials: true } // required for the cookie
6255             })
6256                 .done(function(data) {
6257                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6258                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6259                         data = null;
6260                     }
6261
6262                     if(typeof callback === 'function')
6263                         callback(data);
6264                 })
6265                 .fail(function() {
6266                     NETDATA.error(414, NETDATA.registry.server);
6267
6268                     if(typeof callback === 'function')
6269                         callback(null);
6270                 });
6271         }
6272     };
6273
6274     // ----------------------------------------------------------------------------------------------------------------
6275     // Boot it!
6276
6277     if(typeof netdataPrepCallback === 'function')
6278         netdataPrepCallback();
6279
6280     NETDATA.errorReset();
6281     NETDATA.loadRequiredCSS(0);
6282
6283     NETDATA._loadjQuery(function() {
6284         NETDATA.loadRequiredJs(0, function() {
6285             if(typeof $().emulateTransitionEnd !== 'function') {
6286                 // bootstrap is not available
6287                 NETDATA.options.current.show_help = false;
6288             }
6289
6290             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6291                 if(NETDATA.options.debug.main_loop === true)
6292                     console.log('starting chart refresh thread');
6293
6294                 NETDATA.start();
6295             }
6296         });
6297     });
6298
6299     // window.NETDATA = NETDATA;
6300 // })(window, document);