1 // You can set the following variables before loading this script:
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)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
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
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
33 var NETDATA = window.NETDATA || {};
35 (function(window, document, undefined) {
36 // ------------------------------------------------------------------------
37 // compatibility fixes
39 // fix IE issue with console
40 if(!window.console) { window.console = { log: function(){} }; }
42 // if string.endsWith is not defined, define it
43 if(typeof String.prototype.endsWith !== 'function') {
44 String.prototype.endsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(-s.length) === s;
50 // if string.startsWith is not defined, define it
51 if(typeof String.prototype.startsWith !== 'function') {
52 String.prototype.startsWith = function(s) {
53 if(s.length > this.length) return false;
54 return this.slice(s.length) === s;
58 NETDATA.name2id = function(s) {
67 // ----------------------------------------------------------------------------------------------------------------
68 // Detect the netdata server
70 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
71 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
72 NETDATA._scriptSource = function() {
75 if(typeof document.currentScript !== 'undefined') {
76 script = document.currentScript;
79 var all_scripts = document.getElementsByTagName('script');
80 script = all_scripts[all_scripts.length - 1];
83 if (typeof script.getAttribute.length !== 'undefined')
86 script = script.getAttribute('src', -1);
91 if(typeof netdataServer !== 'undefined')
92 NETDATA.serverDefault = netdataServer;
94 var s = NETDATA._scriptSource();
95 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97 console.log('WARNING: Cannot detect the URL of the netdata server.');
98 NETDATA.serverDefault = null;
102 if(NETDATA.serverDefault === null)
103 NETDATA.serverDefault = '';
104 else if(NETDATA.serverDefault.slice(-1) !== '/')
105 NETDATA.serverDefault += '/';
107 // default URLs for all the external files we need
108 // make them RELATIVE so that the whole thing can also be
109 // installed under a web server
110 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
111 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
112 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
113 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
114 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
115 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
116 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
117 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
118 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
119 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
120 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
121 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
122 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
123 NETDATA.google_js = 'https://www.google.com/jsapi';
127 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
128 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
129 background: '#FFFFFF',
130 foreground: '#000000',
133 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
134 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
135 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
136 '#329262', '#3B3EAC' ],
137 easypiechart_track: '#f0f0f0',
138 easypiechart_scale: '#dfe0e0',
139 gauge_pointer: '#C0C0C0',
140 gauge_stroke: '#F0F0F0',
141 gauge_gradient: false
144 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
145 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
146 background: '#272b30',
147 foreground: '#C8C8C8',
150 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
151 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
152 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
153 '#a6a479', '#a66da8' ],
155 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
156 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
157 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
158 '#329262', '#3B3EFF' ],
159 easypiechart_track: '#373b40',
160 easypiechart_scale: '#373b40',
161 gauge_pointer: '#474b50',
162 gauge_stroke: '#373b40',
163 gauge_gradient: false
167 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
168 NETDATA.themes.current = NETDATA.themes[netdataTheme];
170 NETDATA.themes.current = NETDATA.themes.white;
172 NETDATA.colors = NETDATA.themes.current.colors;
174 // these are the colors Google Charts are using
175 // we have them here to attempt emulate their look and feel on the other chart libraries
176 // http://there4.io/2012/05/02/google-chart-color-list/
177 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
178 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
179 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181 // an alternative set
182 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
183 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
184 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186 if(typeof netdataShowHelp === 'undefined')
187 netdataShowHelp = true;
189 if(typeof netdataShowAlarms === 'undefined')
190 netdataShowAlarms = false;
192 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
193 netdataRegistryAfterMs = 1500;
195 if(typeof netdataRegistry === 'undefined') {
196 // backward compatibility
197 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
198 netdataRegistry = true;
200 netdataRegistry = false;
202 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
203 netdataRegistry = true;
205 // ----------------------------------------------------------------------------------------------------------------
206 // the defaults for all charts
208 // if the user does not specify any of these, the following will be used
210 NETDATA.chartDefaults = {
211 host: NETDATA.serverDefault, // the server to get data from
212 width: '100%', // the chart width - can be null
213 height: '100%', // the chart height - can be null
214 min_width: null, // the chart minimum width - can be null
215 library: 'dygraph', // the graphing library to use
216 method: 'average', // the grouping method
217 before: 0, // panning
218 after: -600, // panning
219 pixels_per_point: 1, // the detail of the chart
220 fill_luminance: 0.8 // luminance of colors in solit areas
223 // ----------------------------------------------------------------------------------------------------------------
227 pauseCallback: null, // a callback when we are really paused
229 pause: false, // when enabled we don't auto-refresh the charts
231 targets: null, // an array of all the state objects that are
232 // currently active (independently of their
233 // viewport visibility)
235 updated_dom: true, // when true, the DOM has been updated with
236 // new elements we have to check.
238 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
239 // rendering charts continiously.
240 // used with .current.fast_render_timeframe
242 page_is_visible: true, // when true, this page is visible
244 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
245 // auto-refresher for some time (when a chart is
246 // performing pan or zoom, we need to stop refreshing
247 // all other charts, to have the maximum speed for
248 // rendering the chart that is panned or zoomed).
249 // Used with .current.global_pan_sync_time
251 last_resized: Date.now(), // the timestamp of the last resize request
253 last_page_scroll: 0, // the timestamp the last time the page was scrolled
255 // the current profile
256 // we may have many...
258 pixels_per_point: 1, // the minimum pixels per point for all charts
259 // increase this to speed javascript up
260 // each chart library has its own limit too
261 // the max of this and the chart library is used
262 // the final is calculated every time, so a change
263 // here will have immediate effect on the next chart
266 idle_between_charts: 100, // ms - how much time to wait between chart updates
268 fast_render_timeframe: 200, // ms - render continously until this time of continious
269 // rendering has been reached
270 // this setting is used to make it render e.g. 10
271 // charts at once, sleep idle_between_charts time
272 // and continue for another 10 charts.
274 idle_between_loops: 500, // ms - if all charts have been updated, wait this
275 // time before starting again.
277 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
279 idle_lost_focus: 500, // ms - when the window does not have focus, check
280 // if focus has been regained, every this time
282 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
283 // autorefreshing of charts is paused for this amount
286 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
287 // of time before setting up synchronized selections
290 sync_selection: true, // enable or disable selection sync
292 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
294 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
296 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298 update_only_visible: true, // enable or disable visibility management
300 parallel_refresher: true, // enable parallel refresh of charts
302 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304 destroy_on_hide: false, // destroy charts when they are not visible
306 show_help: netdataShowHelp, // when enabled the charts will show some help
307 show_help_delay_show_ms: 500,
308 show_help_delay_hide_ms: 0,
310 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
313 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
315 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
317 smooth_plot: true, // enable smooth plot, where possible
319 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321 color_fill_opacity_line: 1.0,
322 color_fill_opacity_area: 0.2,
323 color_fill_opacity_stacked: 0.8,
325 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
326 pan_and_zoom_factor_multiplier_control: 2.0,
327 pan_and_zoom_factor_multiplier_shift: 3.0,
328 pan_and_zoom_factor_multiplier_alt: 4.0,
330 abort_ajax_on_scroll: false, // kill pending ajax page scroll
331 async_on_scroll: false, // sync/async onscroll handler
332 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
336 setOptionCallback: function() { ; }
344 chart_data_url: false,
345 chart_errors: false, // FIXME
353 NETDATA.statistics = {
356 refreshes_active_max: 0
360 // ----------------------------------------------------------------------------------------------------------------
361 // local storage options
363 NETDATA.localStorage = {
366 callback: {} // only used for resetting back to defaults
369 NETDATA.localStorageGet = function(key, def, callback) {
372 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
373 NETDATA.localStorage.default[key.toString()] = def;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
379 // console.log('localStorage: loading "' + key.toString() + '"');
380 ret = localStorage.getItem(key.toString());
381 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
382 if(ret === null || ret === 'undefined') {
383 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
384 localStorage.setItem(key.toString(), JSON.stringify(def));
388 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
389 ret = JSON.parse(ret);
390 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
394 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
399 if(typeof ret === 'undefined' || ret === 'undefined') {
400 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
404 NETDATA.localStorage.current[key.toString()] = ret;
408 NETDATA.localStorageSet = function(key, value, callback) {
409 if(typeof value === 'undefined' || value === 'undefined') {
410 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
413 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
414 NETDATA.localStorage.default[key.toString()] = value;
415 NETDATA.localStorage.current[key.toString()] = value;
416 NETDATA.localStorage.callback[key.toString()] = callback;
419 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
420 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(value));
425 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
429 NETDATA.localStorage.current[key.toString()] = value;
433 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
435 if(typeof obj[i] === 'object') {
436 //console.log('object ' + prefix + '.' + i.toString());
437 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
441 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
445 NETDATA.setOption = function(key, value) {
446 if(key.toString() === 'setOptionCallback') {
447 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
448 NETDATA.options.current[key.toString()] = value;
449 NETDATA.options.current.setOptionCallback();
452 else if(NETDATA.options.current[key.toString()] !== value) {
453 var name = 'options.' + key.toString();
455 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
456 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
458 //console.log(NETDATA.localStorage);
459 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
460 //console.log(NETDATA.options);
461 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
463 if(typeof NETDATA.options.current.setOptionCallback === 'function')
464 NETDATA.options.current.setOptionCallback();
470 NETDATA.getOption = function(key) {
471 return NETDATA.options.current[key.toString()];
474 // read settings from local storage
475 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
477 // always start with this option enabled.
478 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
480 NETDATA.resetOptions = function() {
481 for(var i in NETDATA.localStorage.default) {
482 var a = i.split('.');
484 if(a[0] === 'options') {
485 if(a[1] === 'setOptionCallback') continue;
486 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
487 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
489 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
491 else if(a[0] === 'chart_heights') {
492 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
493 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
499 // ----------------------------------------------------------------------------------------------------------------
501 if(NETDATA.options.debug.main_loop === true)
502 console.log('welcome to NETDATA');
504 NETDATA.onresizeCallback = null;
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
509 if(typeof NETDATA.onresizeCallback === 'function')
510 NETDATA.onresizeCallback();
513 NETDATA.onscroll_updater_count = 0;
514 NETDATA.onscroll_updater_running = false;
515 NETDATA.onscroll_updater_last_run = 0;
516 NETDATA.onscroll_updater_watchdog = null;
517 NETDATA.onscroll_updater_max_duration = 0;
518 NETDATA.onscroll_updater_above_threshold_count = 0;
519 NETDATA.onscroll_updater = function() {
520 NETDATA.onscroll_updater_running = true;
521 NETDATA.onscroll_updater_count++;
522 var start = Date.now();
524 var targets = NETDATA.options.targets;
525 var len = targets.length;
527 // when the user scrolls he sees that we have
528 // hidden all the not-visible charts
529 // using this little function we try to switch
530 // the charts back to visible quickly
533 if(NETDATA.options.abort_ajax_on_scroll === true) {
534 // we have to cancel pending requests too
537 if (targets[len]._updating === true) {
538 if (typeof targets[len].xhr !== 'undefined') {
539 targets[len].xhr.abort();
540 targets[len].running = false;
541 targets[len]._updating = false;
543 targets[len].isVisible();
548 // just find which chart is visible
551 targets[len].isVisible();
554 var end = Date.now();
555 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
557 if(NETDATA.options.current.async_on_scroll === false) {
558 var dt = end - start;
559 if(dt > NETDATA.onscroll_updater_max_duration) {
560 // console.log('max onscroll event handler duration increased to ' + dt);
561 NETDATA.onscroll_updater_max_duration = dt;
564 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
565 // console.log('slow: ' + dt);
566 NETDATA.onscroll_updater_above_threshold_count++;
568 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
569 NETDATA.setOption('async_on_scroll', true);
570 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
575 NETDATA.onscroll_updater_last_run = start;
576 NETDATA.onscroll_updater_running = false;
579 NETDATA.onscroll = function() {
580 // console.log('onscroll');
582 NETDATA.options.last_page_scroll = Date.now();
583 NETDATA.options.auto_refresher_stop_until = 0;
585 if(NETDATA.options.targets === null) return;
587 if(NETDATA.options.current.async_on_scroll === true) {
589 if(NETDATA.onscroll_updater_running === false) {
590 NETDATA.onscroll_updater_running = true;
591 setTimeout(NETDATA.onscroll_updater, 0);
594 if(NETDATA.onscroll_updater_watchdog !== null)
595 clearTimeout(NETDATA.onscroll_updater_watchdog);
597 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
598 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
599 // console.log('watchdog');
600 NETDATA.onscroll_updater();
603 NETDATA.onscroll_updater_watchdog = null;
609 NETDATA.onscroll_updater();
613 window.onresize = NETDATA.onresize;
614 window.onscroll = NETDATA.onscroll;
616 // ----------------------------------------------------------------------------------------------------------------
619 NETDATA.errorCodes = {
620 100: { message: "Cannot load chart library", alert: true },
621 101: { message: "Cannot load jQuery", alert: true },
622 402: { message: "Chart library not found", alert: false },
623 403: { message: "Chart library not enabled/is failed", alert: false },
624 404: { message: "Chart not found", alert: false },
625 405: { message: "Cannot download charts index from server", alert: true },
626 406: { message: "Invalid charts index downloaded from server", alert: true },
627 407: { message: "Cannot HELLO netdata server", alert: false },
628 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
629 409: { message: "Cannot ACCESS netdata registry", alert: false },
630 410: { message: "Netdata registry ACCESS failed", alert: false },
631 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
632 412: { message: "Netdata registry DELETE failed", alert: false },
633 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
634 414: { message: "Netdata registry SWITCH failed", alert: false },
635 415: { message: "Netdata alarms download failed", alert: false },
636 416: { message: "Netdata alarms log download failed", alert: false },
637 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
638 418: { message: "Netdata registry SEARCH failed", alert: false }
640 NETDATA.errorLast = {
646 NETDATA.error = function(code, msg) {
647 NETDATA.errorLast.code = code;
648 NETDATA.errorLast.message = msg;
649 NETDATA.errorLast.datetime = Date.now();
651 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
654 if(typeof netdataErrorCallback === 'function') {
655 ret = netdataErrorCallback('system', code, msg);
658 if(ret && NETDATA.errorCodes[code].alert)
659 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
662 NETDATA.errorReset = function() {
663 NETDATA.errorLast.code = 0;
664 NETDATA.errorLast.message = "You are doing fine!";
665 NETDATA.errorLast.datetime = 0;
668 // ----------------------------------------------------------------------------------------------------------------
669 // commonMin & commonMax
671 NETDATA.commonMin = {
675 get: function(state) {
676 if(typeof state.__commonMin === 'undefined') {
677 // get the commonMin setting
678 var self = $(state.element);
679 state.__commonMin = self.data('common-min') || null;
682 var min = state.data.min;
683 var name = state.__commonMin;
686 // we don't need commonMin
687 //state.log('no need for commonMin');
691 var t = this.keys[name];
692 if(typeof t === 'undefined') {
694 this.keys[name] = {};
698 var uuid = state.uuid;
699 if(typeof t[uuid] !== 'undefined') {
700 if(t[uuid] === min) {
701 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
702 return this.latest[name];
704 else if(min < this.latest[name]) {
705 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
707 this.latest[name] = min;
715 // find the common min
718 if(t[i] < m) m = t[i];
720 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
721 this.latest[name] = m;
726 NETDATA.commonMax = {
730 get: function(state) {
731 if(typeof state.__commonMax === 'undefined') {
732 // get the commonMax setting
733 var self = $(state.element);
734 state.__commonMax = self.data('common-max') || null;
737 var max = state.data.max;
738 var name = state.__commonMax;
741 // we don't need commonMax
742 //state.log('no need for commonMax');
746 var t = this.keys[name];
747 if(typeof t === 'undefined') {
749 this.keys[name] = {};
753 var uuid = state.uuid;
754 if(typeof t[uuid] !== 'undefined') {
755 if(t[uuid] === max) {
756 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
757 return this.latest[name];
759 else if(max > this.latest[name]) {
760 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
762 this.latest[name] = max;
770 // find the common max
773 if(t[i] > m) m = t[i];
775 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
776 this.latest[name] = m;
781 // ----------------------------------------------------------------------------------------------------------------
784 // When multiple charts need the same chart, we avoid downloading it
785 // multiple times (and having it in browser memory multiple time)
786 // by using this registry.
788 // Every time we download a chart definition, we save it here with .add()
789 // Then we try to get it back with .get(). If that fails, we download it.
791 NETDATA.chartRegistry = {
794 fixid: function(id) {
795 return id.replace(/:/g, "_").replace(/\//g, "_");
798 add: function(host, id, data) {
799 host = this.fixid(host);
802 if(typeof this.charts[host] === 'undefined')
803 this.charts[host] = {};
805 //console.log('added ' + host + '/' + id);
806 this.charts[host][id] = data;
809 get: function(host, id) {
810 host = this.fixid(host);
813 if(typeof this.charts[host] === 'undefined')
816 if(typeof this.charts[host][id] === 'undefined')
819 //console.log('cached ' + host + '/' + id);
820 return this.charts[host][id];
823 downloadAll: function(host, callback) {
824 while(host.slice(-1) === '/')
825 host = host.substring(0, host.length - 1);
830 url: host + '/api/v1/charts',
833 xhrFields: { withCredentials: true } // required for the cookie
835 .done(function(data) {
837 var h = NETDATA.chartRegistry.fixid(host);
838 self.charts[h] = data.charts;
840 else NETDATA.error(406, host + '/api/v1/charts');
842 if(typeof callback === 'function')
846 NETDATA.error(405, host + '/api/v1/charts');
848 if(typeof callback === 'function')
854 // ----------------------------------------------------------------------------------------------------------------
855 // Global Pan and Zoom on charts
857 // Using this structure are synchronize all the charts, so that
858 // when you pan or zoom one, all others are automatically refreshed
859 // to the same timespan.
861 NETDATA.globalPanAndZoom = {
862 seq: 0, // timestamp ms
863 // every time a chart is panned or zoomed
864 // we set the timestamp here
865 // then we use it as a sequence number
866 // to find if other charts are syncronized
869 master: null, // the master chart (state), to which all others
872 force_before_ms: null, // the timespan to sync all other charts
873 force_after_ms: null,
878 setMaster: function(state, after, before) {
879 if(NETDATA.options.current.sync_pan_and_zoom === false)
882 if(this.master !== null && this.master !== state)
883 this.master.resetChart(true, true);
885 var now = Date.now();
888 this.force_after_ms = after;
889 this.force_before_ms = before;
890 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
892 if(typeof this.callback === 'function')
893 this.callback(true, after, before);
897 clearMaster: function() {
898 if(this.master !== null) {
899 var st = this.master;
906 this.force_after_ms = null;
907 this.force_before_ms = null;
908 NETDATA.options.auto_refresher_stop_until = 0;
910 if(typeof this.callback === 'function')
911 this.callback(false, 0, 0);
914 // is the given state the master of the global
915 // pan and zoom sync?
916 isMaster: function(state) {
917 if(this.master === state) return true;
921 // are we currently have a global pan and zoom sync?
922 isActive: function() {
923 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
927 // check if a chart, other than the master
928 // needs to be refreshed, due to the global pan and zoom
929 shouldBeAutoRefreshed: function(state) {
930 if(this.master === null || this.seq === 0)
933 //if(state.needsRecreation())
936 if(state.tm.pan_and_zoom_seq === this.seq)
943 // ----------------------------------------------------------------------------------------------------------------
944 // dimensions selection
947 // move color assignment to dimensions, here
949 dimensionStatus = function(parent, label, name_div, value_div, color) {
950 this.enabled = false;
951 this.parent = parent;
953 this.name_div = null;
954 this.value_div = null;
955 this.color = NETDATA.themes.current.foreground;
957 if(parent.unselected_count === 0)
958 this.selected = true;
960 this.selected = false;
962 this.setOptions(name_div, value_div, color);
965 dimensionStatus.prototype.invalidate = function() {
966 this.name_div = null;
967 this.value_div = null;
968 this.enabled = false;
971 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
974 if(this.name_div != name_div) {
975 this.name_div = name_div;
976 this.name_div.title = this.label;
977 this.name_div.style.color = this.color;
978 if(this.selected === false)
979 this.name_div.className = 'netdata-legend-name not-selected';
981 this.name_div.className = 'netdata-legend-name selected';
984 if(this.value_div != value_div) {
985 this.value_div = value_div;
986 this.value_div.title = this.label;
987 this.value_div.style.color = this.color;
988 if(this.selected === false)
989 this.value_div.className = 'netdata-legend-value not-selected';
991 this.value_div.className = 'netdata-legend-value selected';
998 dimensionStatus.prototype.setHandler = function() {
999 if(this.enabled === false) return;
1003 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1004 this.name_div.onclick = this.value_div.onclick = function(e) {
1006 if(ds.isSelected()) {
1008 if(e.shiftKey === true || e.ctrlKey === true) {
1009 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1012 if(ds.parent.countSelected() === 0)
1013 ds.parent.selectAll();
1016 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1017 if(ds.parent.countSelected() === 1) {
1018 ds.parent.selectAll();
1021 ds.parent.selectNone();
1027 // this is not selected
1028 if(e.shiftKey === true || e.ctrlKey === true) {
1029 // control or shift key is pressed -> select this too
1033 // no key is pressed -> select only this
1034 ds.parent.selectNone();
1039 ds.parent.state.redrawChart();
1043 dimensionStatus.prototype.select = function() {
1044 if(this.enabled === false) return;
1046 this.name_div.className = 'netdata-legend-name selected';
1047 this.value_div.className = 'netdata-legend-value selected';
1048 this.selected = true;
1051 dimensionStatus.prototype.unselect = function() {
1052 if(this.enabled === false) return;
1054 this.name_div.className = 'netdata-legend-name not-selected';
1055 this.value_div.className = 'netdata-legend-value hidden';
1056 this.selected = false;
1059 dimensionStatus.prototype.isSelected = function() {
1060 return(this.enabled === true && this.selected === true);
1063 // ----------------------------------------------------------------------------------------------------------------
1065 dimensionsVisibility = function(state) {
1068 this.dimensions = {};
1069 this.selected_count = 0;
1070 this.unselected_count = 0;
1073 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1074 if(typeof this.dimensions[label] === 'undefined') {
1076 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1079 this.dimensions[label].setOptions(name_div, value_div, color);
1081 return this.dimensions[label];
1084 dimensionsVisibility.prototype.dimensionGet = function(label) {
1085 return this.dimensions[label];
1088 dimensionsVisibility.prototype.invalidateAll = function() {
1089 for(var d in this.dimensions)
1090 this.dimensions[d].invalidate();
1093 dimensionsVisibility.prototype.selectAll = function() {
1094 for(var d in this.dimensions)
1095 this.dimensions[d].select();
1098 dimensionsVisibility.prototype.countSelected = function() {
1100 for(var d in this.dimensions)
1101 if(this.dimensions[d].isSelected()) i++;
1106 dimensionsVisibility.prototype.selectNone = function() {
1107 for(var d in this.dimensions)
1108 this.dimensions[d].unselect();
1111 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1112 var ret = new Array();
1113 this.selected_count = 0;
1114 this.unselected_count = 0;
1116 var len = array.length;
1118 var ds = this.dimensions[array[len]];
1119 if(typeof ds === 'undefined') {
1120 // console.log(array[i] + ' is not found');
1123 else if(ds.isSelected()) {
1125 this.selected_count++;
1129 this.unselected_count++;
1133 if(this.selected_count === 0 && this.unselected_count !== 0) {
1135 return this.selected2BooleanArray(array);
1142 // ----------------------------------------------------------------------------------------------------------------
1143 // global selection sync
1145 NETDATA.globalSelectionSync = {
1147 dont_sync_before: 0,
1152 if(this.state !== null)
1153 this.state.globalSelectionSyncStop();
1157 if(this.state !== null) {
1158 this.state.globalSelectionSyncDelay();
1163 // ----------------------------------------------------------------------------------------------------------------
1164 // http://wilsonpage.co.uk/preventing-layout-thrashing/
1166 NETDATA.noLayoutTrashing = {
1170 add: function(callback) {
1171 // console.log('adding...');
1172 this.callbacks.push(callback);
1174 if(this.set === false) {
1178 window.requestAnimationFrame(function() {
1179 var len = that.callbacks.length;
1180 // console.log('running... ' + len.toString());
1182 that.callbacks[len]();
1185 that.callbacks = new Array();
1192 // ----------------------------------------------------------------------------------------------------------------
1193 // Our state object, where all per-chart values are stored
1195 chartState = function(element) {
1196 var self = $(element);
1197 this.element = element;
1200 // all private functions should use 'that', instead of 'this'
1203 /* error() - private
1204 * show an error instead of the chart
1206 var error = function(msg) {
1209 if(typeof netdataErrorCallback === 'function') {
1210 ret = netdataErrorCallback('chart', that.id, msg);
1214 that.element.innerHTML = that.id + ': ' + msg;
1215 that.enabled = false;
1216 that.current = that.pan;
1220 // GUID - a unique identifier for the chart
1221 this.uuid = NETDATA.guid();
1223 // string - the name of chart
1224 this.id = self.data('netdata');
1226 // string - the key for localStorage settings
1227 this.settings_id = self.data('id') || null;
1229 // the user given dimensions of the element
1230 this.width = self.data('width') || NETDATA.chartDefaults.width;
1231 this.height = self.data('height') || NETDATA.chartDefaults.height;
1232 this.height_original = this.height;
1234 if(this.settings_id !== null) {
1235 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1236 // this is the callback that will be called
1237 // if and when the user resets all localStorage variables
1238 // to their defaults
1240 resizeChartToHeight(height);
1244 // string - the netdata server URL, without any path
1245 this.host = self.data('host') || NETDATA.chartDefaults.host;
1247 // make sure the host does not end with /
1248 // all netdata API requests use absolute paths
1249 while(this.host.slice(-1) === '/')
1250 this.host = this.host.substring(0, this.host.length - 1);
1252 // string - the grouping method requested by the user
1253 this.method = self.data('method') || NETDATA.chartDefaults.method;
1255 // the time-range requested by the user
1256 this.after = self.data('after') || NETDATA.chartDefaults.after;
1257 this.before = self.data('before') || NETDATA.chartDefaults.before;
1259 // the pixels per point requested by the user
1260 this.pixels_per_point = self.data('pixels-per-point') || 1;
1261 this.points = self.data('points') || null;
1263 // the dimensions requested by the user
1264 this.dimensions = self.data('dimensions') || null;
1266 // the chart library requested by the user
1267 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1269 // how many retries we have made to load chart data from the server
1270 this.retries_on_data_failures = 0;
1272 // object - the chart library used
1273 this.library = null;
1277 this.colors_assigned = {};
1278 this.colors_available = null;
1280 // the element already created by the user
1281 this.element_message = null;
1283 // the element with the chart
1284 this.element_chart = null;
1286 // the element with the legend of the chart (if created by us)
1287 this.element_legend = null;
1288 this.element_legend_childs = {
1293 perfect_scroller: null, // the container to apply perfect scroller to
1297 this.chart_url = null; // string - the url to download chart info
1298 this.chart = null; // object - the chart as downloaded from the server
1300 this.title = self.data('title') || null; // the title of the chart
1301 this.units = self.data('units') || null; // the units of the chart dimensions
1302 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1303 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1305 this.running = false; // boolean - true when the chart is being refreshed now
1306 this.validated = false; // boolean - has the chart been validated?
1307 this.enabled = true; // boolean - is the chart enabled for refresh?
1308 this.paused = false; // boolean - is the chart paused for any reason?
1309 this.selected = false; // boolean - is the chart shown a selection?
1310 this.debug = false; // boolean - console.log() debug info about this chart
1312 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1313 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1314 this.requested_after = null; // milliseconds - the timestamp of the request after param
1315 this.requested_before = null; // milliseconds - the timestamp of the request before param
1316 this.requested_padding = null;
1317 this.view_after = 0;
1318 this.view_before = 0;
1320 this.value_decimal_detail = -1;
1322 var d = self.data('decimal-digits');
1323 if(typeof d === 'number') {
1324 this.value_decimal_detail = 1;
1326 this.value_decimal_detail *= 10;
1333 force_update_at: 0, // the timestamp to force the update at
1334 force_before_ms: null,
1335 force_after_ms: null
1340 force_update_at: 0, // the timestamp to force the update at
1341 force_before_ms: null,
1342 force_after_ms: null
1347 force_update_at: 0, // the timestamp to force the update at
1348 force_before_ms: null,
1349 force_after_ms: null
1352 // this is a pointer to one of the sub-classes below
1354 this.current = this.auto;
1356 // check the requested library is available
1357 // we don't initialize it here - it will be initialized when
1358 // this chart will be first used
1359 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1360 NETDATA.error(402, that.library_name);
1361 error('chart library "' + that.library_name + '" is not found');
1364 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1365 NETDATA.error(403, that.library_name);
1366 error('chart library "' + that.library_name + '" is not enabled');
1370 that.library = NETDATA.chartLibraries[that.library_name];
1372 // milliseconds - the time the last refresh took
1373 this.refresh_dt_ms = 0;
1375 // if we need to report the rendering speed
1376 // find the element that needs to be updated
1377 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1379 if(refresh_dt_element_name !== null)
1380 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1382 this.refresh_dt_element = null;
1384 this.dimensions_visibility = new dimensionsVisibility(this);
1386 this._updating = false;
1388 // ============================================================================================================
1389 // PRIVATE FUNCTIONS
1391 var createDOM = function() {
1392 if(that.enabled === false) return;
1394 if(that.element_message !== null) that.element_message.innerHTML = '';
1395 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1396 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1398 that.element.innerHTML = '';
1400 that.element_message = document.createElement('div');
1401 that.element_message.className = ' netdata-message hidden';
1402 that.element.appendChild(that.element_message);
1404 that.element_chart = document.createElement('div');
1405 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1406 that.element.appendChild(that.element_chart);
1408 if(that.hasLegend() === true) {
1409 that.element.className = "netdata-container-with-legend";
1410 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1412 that.element_legend = document.createElement('div');
1413 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1414 that.element.appendChild(that.element_legend);
1417 that.element.className = "netdata-container";
1418 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1420 that.element_legend = null;
1422 that.element_legend_childs.series = null;
1424 if(typeof(that.width) === 'string')
1425 $(that.element).css('width', that.width);
1426 else if(typeof(that.width) === 'number')
1427 $(that.element).css('width', that.width + 'px');
1429 if(typeof(that.library.aspect_ratio) === 'undefined') {
1430 if(typeof(that.height) === 'string')
1431 $(that.element).css('height', that.height);
1432 else if(typeof(that.height) === 'number')
1433 $(that.element).css('height', that.height + 'px');
1436 var w = that.element.offsetWidth;
1437 if(w === null || w === 0) {
1438 // the div is hidden
1439 // this will resize the chart when next viewed
1440 that.tm.last_resized = 0;
1443 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1446 if(NETDATA.chartDefaults.min_width !== null)
1447 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1449 that.tm.last_dom_created = Date.now();
1455 * initialize state variables
1456 * destroy all (possibly) created state elements
1457 * create the basic DOM for a chart
1459 var init = function() {
1460 if(that.enabled === false) return;
1462 that.paused = false;
1463 that.selected = false;
1465 that.chart_created = false; // boolean - is the library.create() been called?
1466 that.updates_counter = 0; // numeric - the number of refreshes made so far
1467 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1468 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1471 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1472 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1473 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1475 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1476 last_updated: 0, // the timestamp the chart last updated with data
1477 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1479 // Used with NETDATA.globalPanAndZoom.seq
1480 last_visible_check: 0, // the time we last checked if it is visible
1481 last_resized: 0, // the time the chart was resized
1482 last_hidden: 0, // the time the chart was hidden
1483 last_unhidden: 0, // the time the chart was unhidden
1484 last_autorefreshed: 0 // the time the chart was last refreshed
1487 that.data = null; // the last data as downloaded from the netdata server
1488 that.data_url = 'invalid://'; // string - the last url used to update the chart
1489 that.data_points = 0; // number - the number of points returned from netdata
1490 that.data_after = 0; // milliseconds - the first timestamp of the data
1491 that.data_before = 0; // milliseconds - the last timestamp of the data
1492 that.data_update_every = 0; // milliseconds - the frequency to update the data
1494 that.tm.last_initialized = Date.now();
1497 that.setMode('auto');
1500 var maxMessageFontSize = function() {
1501 var screenHeight = screen.height;
1502 var el = that.element_message;
1504 // normally we want a font size, as tall as the element
1505 var h = el.clientHeight;
1507 // but give it some air, 20% let's say, or 5 pixels min
1508 var lost = Math.max(h * 0.2, 5);
1511 // center the text, vertically
1512 var paddingTop = (lost - 5) / 2;
1514 // but check the width too
1515 // it should fit 10 characters in it
1516 var w = el.clientWidth / 10;
1518 paddingTop += (h - w) / 2;
1522 // and don't make it too huge
1523 // 5% of the screen size is good
1524 if(h > screenHeight / 20) {
1525 paddingTop += (h - (screenHeight / 20)) / 2;
1526 h = screenHeight / 20;
1530 el.style.fontSize = h.toString() + 'px';
1531 el.style.paddingTop = paddingTop.toString() + 'px';
1534 var showMessage = function(msg) {
1535 that.element_message.className = 'netdata-message';
1536 that.element_message.innerHTML = msg;
1537 that.element_message.style.fontSize = 'x-small';
1538 that.element_message.style.paddingTop = '0px';
1539 that.___messageHidden___ = undefined;
1542 var showMessageIcon = function(icon) {
1543 NETDATA.noLayoutTrashing.add(function() {
1544 that.element_message.innerHTML = icon;
1545 that.element_message.className = 'netdata-message icon';
1546 maxMessageFontSize();
1547 that.___messageHidden___ = undefined;
1551 var hideMessage = function() {
1552 if(typeof that.___messageHidden___ === 'undefined') {
1553 that.___messageHidden___ = true;
1554 that.element_message.className = 'netdata-message hidden';
1558 var showRendering = function() {
1560 if(that.chart !== null) {
1561 if(that.chart.chart_type === 'line')
1562 icon = '<i class="fa fa-line-chart"></i>';
1564 icon = '<i class="fa fa-area-chart"></i>';
1567 icon = '<i class="fa fa-area-chart"></i>';
1569 showMessageIcon(icon + ' netdata');
1572 var showLoading = function() {
1573 if(that.chart_created === false) {
1574 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1580 var isHidden = function() {
1581 if(typeof that.___chartIsHidden___ !== 'undefined')
1587 // hide the chart, when it is not visible - called from isVisible()
1588 var hideChart = function() {
1589 // hide it, if it is not already hidden
1590 if(isHidden() === true) return;
1592 if(that.chart_created === true) {
1593 if(NETDATA.options.current.destroy_on_hide === true) {
1594 // we should destroy it
1599 that.element_chart.style.display = 'none';
1600 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1601 that.tm.last_hidden = Date.now();
1604 // This works, but I not sure there are no corner cases somewhere
1605 // so it is commented - if the user has memory issues he can
1606 // set Destroy on Hide for all charts
1607 // that.data = null;
1611 that.___chartIsHidden___ = true;
1614 // unhide the chart, when it is visible - called from isVisible()
1615 var unhideChart = function() {
1616 if(isHidden() === false) return;
1618 that.___chartIsHidden___ = undefined;
1619 that.updates_since_last_unhide = 0;
1621 if(that.chart_created === false) {
1622 // we need to re-initialize it, to show our background
1623 // logo in bootstrap tabs, until the chart loads
1627 that.tm.last_unhidden = Date.now();
1628 that.element_chart.style.display = '';
1629 if(that.element_legend !== null) that.element_legend.style.display = '';
1635 var canBeRendered = function() {
1636 if(isHidden() === true || that.isVisible(true) === false)
1642 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1643 var callChartLibraryUpdateSafely = function(data) {
1646 if(canBeRendered() === false)
1649 if(NETDATA.options.debug.chart_errors === true)
1650 status = that.library.update(that, data);
1653 status = that.library.update(that, data);
1660 if(status === false) {
1661 error('chart failed to be updated as ' + that.library_name);
1668 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1669 var callChartLibraryCreateSafely = function(data) {
1672 if(canBeRendered() === false)
1675 if(NETDATA.options.debug.chart_errors === true)
1676 status = that.library.create(that, data);
1679 status = that.library.create(that, data);
1686 if(status === false) {
1687 error('chart failed to be created as ' + that.library_name);
1691 that.chart_created = true;
1692 that.updates_since_last_creation = 0;
1696 // ----------------------------------------------------------------------------------------------------------------
1699 // resizeChart() - private
1700 // to be called just before the chart library to make sure that
1701 // a properly sized dom is available
1702 var resizeChart = function() {
1703 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1704 if(that.chart_created === false) return;
1706 if(that.needsRecreation()) {
1709 else if(typeof that.library.resize === 'function') {
1710 that.library.resize(that);
1712 if(that.element_legend_childs.perfect_scroller !== null)
1713 Ps.update(that.element_legend_childs.perfect_scroller);
1715 maxMessageFontSize();
1718 that.tm.last_resized = Date.now();
1722 // this is the actual chart resize algorithm
1724 // - resize the entire container
1725 // - update the internal states
1726 // - resize the chart as the div changes height
1727 // - update the scrollbar of the legend
1728 var resizeChartToHeight = function(h) {
1730 that.element.style.height = h;
1732 if(that.settings_id !== null)
1733 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1735 var now = Date.now();
1736 NETDATA.options.last_page_scroll = now;
1737 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1740 that.tm.last_resized = 0;
1744 this.resizeHandler = function(e) {
1747 if(typeof this.event_resize === 'undefined'
1748 || this.event_resize.chart_original_w === 'undefined'
1749 || this.event_resize.chart_original_h === 'undefined')
1750 this.event_resize = {
1751 chart_original_w: this.element.clientWidth,
1752 chart_original_h: this.element.clientHeight,
1756 if(e.type === 'touchstart') {
1757 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1758 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1761 this.event_resize.mouse_start_x = e.clientX;
1762 this.event_resize.mouse_start_y = e.clientY;
1765 this.event_resize.chart_start_w = this.element.clientWidth;
1766 this.event_resize.chart_start_h = this.element.clientHeight;
1767 this.event_resize.chart_last_w = this.element.clientWidth;
1768 this.event_resize.chart_last_h = this.element.clientHeight;
1770 var now = Date.now();
1771 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller != null) {
1772 // double click / double tap event
1774 // console.dir(this.element_legend_childs.content);
1775 // console.dir(this.element_legend_childs.perfect_scroller);
1777 // the optimal height of the chart
1778 // showing the entire legend
1779 var optimal = this.event_resize.chart_last_h
1780 + this.element_legend_childs.perfect_scroller.scrollHeight
1781 - this.element_legend_childs.perfect_scroller.clientHeight;
1783 // if we are not optimal, be optimal
1784 if(this.event_resize.chart_last_h != optimal) {
1785 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1786 resizeChartToHeight(optimal.toString() + 'px');
1789 // else if the current height is not the original/saved height
1790 // reset to the original/saved height
1791 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h) {
1792 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1793 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1796 // else if the current height is not the internal default height
1797 // reset to the internal default height
1798 else if((this.event_resize.chart_last_h.toString() + 'px') != this.height_original) {
1799 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1800 resizeChartToHeight(this.height_original.toString());
1803 // else if the current height is not the firstchild's clientheight
1805 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1806 var parent_rect = this.element.getBoundingClientRect();
1807 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1808 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1810 // console.log(parent_rect);
1811 // console.log(content_rect);
1812 // console.log(wanted);
1814 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1815 if(this.event_resize.chart_last_h != wanted)
1816 resizeChartToHeight(wanted.toString() + 'px');
1820 this.event_resize.last = now;
1822 // process movement event
1823 document.onmousemove =
1824 document.ontouchmove =
1825 this.element_legend_childs.resize_handler.onmousemove =
1826 this.element_legend_childs.resize_handler.ontouchmove =
1831 case 'mousemove': y = e.clientY; break;
1832 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1836 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1838 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1839 resizeChartToHeight(newH.toString() + 'px');
1840 that.event_resize.chart_last_h = newH;
1845 // process end event
1846 document.onmouseup =
1847 document.ontouchend =
1848 this.element_legend_childs.resize_handler.onmouseup =
1849 this.element_legend_childs.resize_handler.ontouchend =
1851 // remove all the hooks
1852 document.onmouseup =
1853 document.onmousemove =
1854 document.ontouchmove =
1855 document.ontouchend =
1856 that.element_legend_childs.resize_handler.onmousemove =
1857 that.element_legend_childs.resize_handler.ontouchmove =
1858 that.element_legend_childs.resize_handler.onmouseout =
1859 that.element_legend_childs.resize_handler.onmouseup =
1860 that.element_legend_childs.resize_handler.ontouchend =
1863 // allow auto-refreshes
1864 NETDATA.options.auto_refresher_stop_until = 0;
1870 var noDataToShow = function() {
1871 showMessageIcon('<i class="fa fa-warning"></i> empty');
1872 that.legendUpdateDOM();
1873 that.tm.last_autorefreshed = Date.now();
1874 // that.data_update_every = 30 * 1000;
1875 //that.element_chart.style.display = 'none';
1876 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1877 //that.___chartIsHidden___ = true;
1880 // ============================================================================================================
1883 this.error = function(msg) {
1887 this.setMode = function(m) {
1888 if(this.current !== null && this.current.name === m) return;
1891 this.current = this.auto;
1892 else if(m === 'pan')
1893 this.current = this.pan;
1894 else if(m === 'zoom')
1895 this.current = this.zoom;
1897 this.current = this.auto;
1899 this.current.force_update_at = 0;
1900 this.current.force_before_ms = null;
1901 this.current.force_after_ms = null;
1903 this.tm.last_mode_switch = Date.now();
1906 // ----------------------------------------------------------------------------------------------------------------
1907 // global selection sync
1909 // prevent to global selection sync for some time
1910 this.globalSelectionSyncDelay = function(ms) {
1911 if(NETDATA.options.current.sync_selection === false)
1914 if(typeof ms === 'number')
1915 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1917 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1920 // can we globally apply selection sync?
1921 this.globalSelectionSyncAbility = function() {
1922 if(NETDATA.options.current.sync_selection === false)
1925 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1931 this.globalSelectionSyncIsMaster = function() {
1932 if(NETDATA.globalSelectionSync.state === this)
1938 // this chart is the master of the global selection sync
1939 this.globalSelectionSyncBeMaster = function() {
1941 if(this.globalSelectionSyncIsMaster()) {
1942 if(this.debug === true)
1943 this.log('sync: I am the master already.');
1948 if(NETDATA.globalSelectionSync.state) {
1949 if(this.debug === true)
1950 this.log('sync: I am not the sync master. Resetting global sync.');
1952 this.globalSelectionSyncStop();
1955 // become the master
1956 if(this.debug === true)
1957 this.log('sync: becoming sync master.');
1959 this.selected = true;
1960 NETDATA.globalSelectionSync.state = this;
1962 // find the all slaves
1963 var targets = NETDATA.options.targets;
1964 var len = targets.length;
1969 if(this.debug === true)
1970 st.log('sync: not adding me to sync');
1972 else if(st.globalSelectionSyncIsEligible()) {
1973 if(this.debug === true)
1974 st.log('sync: adding to sync as slave');
1976 st.globalSelectionSyncBeSlave();
1980 // this.globalSelectionSyncDelay(100);
1983 // can the chart participate to the global selection sync as a slave?
1984 this.globalSelectionSyncIsEligible = function() {
1985 if(this.enabled === true
1986 && this.library !== null
1987 && typeof this.library.setSelection === 'function'
1988 && this.isVisible() === true
1989 && this.chart_created === true)
1995 // this chart becomes a slave of the global selection sync
1996 this.globalSelectionSyncBeSlave = function() {
1997 if(NETDATA.globalSelectionSync.state !== this)
1998 NETDATA.globalSelectionSync.slaves.push(this);
2001 // sync all the visible charts to the given time
2002 // this is to be called from the chart libraries
2003 this.globalSelectionSync = function(t) {
2004 if(this.globalSelectionSyncAbility() === false) {
2005 if(this.debug === true)
2006 this.log('sync: cannot sync (yet?).');
2011 if(this.globalSelectionSyncIsMaster() === false) {
2012 if(this.debug === true)
2013 this.log('sync: trying to be sync master.');
2015 this.globalSelectionSyncBeMaster();
2017 if(this.globalSelectionSyncAbility() === false) {
2018 if(this.debug === true)
2019 this.log('sync: cannot sync (yet?).');
2025 NETDATA.globalSelectionSync.last_t = t;
2026 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2031 // stop syncing all charts to the given time
2032 this.globalSelectionSyncStop = function() {
2033 if(NETDATA.globalSelectionSync.slaves.length) {
2034 if(this.debug === true)
2035 this.log('sync: cleaning up...');
2037 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2039 if(that.debug === true)
2040 st.log('sync: not adding me to sync stop');
2043 if(that.debug === true)
2044 st.log('sync: removed slave from sync');
2046 st.clearSelection();
2050 NETDATA.globalSelectionSync.last_t = 0;
2051 NETDATA.globalSelectionSync.slaves = [];
2052 NETDATA.globalSelectionSync.state = null;
2055 this.clearSelection();
2058 this.setSelection = function(t) {
2059 if(typeof this.library.setSelection === 'function') {
2060 if(this.library.setSelection(this, t) === true)
2061 this.selected = true;
2063 this.selected = false;
2065 else this.selected = true;
2067 if(this.selected === true && this.debug === true)
2068 this.log('selection set to ' + t.toString());
2070 return this.selected;
2073 this.clearSelection = function() {
2074 if(this.selected === true) {
2075 if(typeof this.library.clearSelection === 'function') {
2076 if(this.library.clearSelection(this) === true)
2077 this.selected = false;
2079 this.selected = true;
2081 else this.selected = false;
2083 if(this.selected === false && this.debug === true)
2084 this.log('selection cleared');
2089 return this.selected;
2092 // find if a timestamp (ms) is shown in the current chart
2093 this.timeIsVisible = function(t) {
2094 if(t >= this.data_after && t <= this.data_before)
2099 this.calculateRowForTime = function(t) {
2100 if(this.timeIsVisible(t) === false) return -1;
2101 return Math.floor((t - this.data_after) / this.data_update_every);
2104 // ----------------------------------------------------------------------------------------------------------------
2107 this.log = function(msg) {
2108 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2111 this.pauseChart = function() {
2112 if(this.paused === false) {
2113 if(this.debug === true)
2114 this.log('pauseChart()');
2120 this.unpauseChart = function() {
2121 if(this.paused === true) {
2122 if(this.debug === true)
2123 this.log('unpauseChart()');
2125 this.paused = false;
2129 this.resetChart = function(dont_clear_master, dont_update) {
2130 if(this.debug === true)
2131 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2133 if(typeof dont_clear_master === 'undefined')
2134 dont_clear_master = false;
2136 if(typeof dont_update === 'undefined')
2137 dont_update = false;
2139 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2140 if(this.debug === true)
2141 this.log('resetChart() diverting to clearMaster().');
2142 // this will call us back with master === true
2143 NETDATA.globalPanAndZoom.clearMaster();
2147 this.clearSelection();
2149 this.tm.pan_and_zoom_seq = 0;
2151 this.setMode('auto');
2152 this.current.force_update_at = 0;
2153 this.current.force_before_ms = null;
2154 this.current.force_after_ms = null;
2155 this.tm.last_autorefreshed = 0;
2156 this.paused = false;
2157 this.selected = false;
2158 this.enabled = true;
2159 // this.debug = false;
2161 // do not update the chart here
2162 // or the chart will flip-flop when it is the master
2163 // of a selection sync and another chart becomes
2166 if(dont_update !== true && this.isVisible() === true) {
2171 this.updateChartPanOrZoom = function(after, before) {
2172 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2175 if(this.debug === true)
2178 if(before < after) {
2179 if(this.debug === true)
2180 this.log(logme + 'flipped parameters, rejecting it.');
2185 if(typeof this.fixed_min_duration === 'undefined')
2186 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2188 var min_duration = this.fixed_min_duration;
2189 var current_duration = Math.round(this.view_before - this.view_after);
2191 // round the numbers
2192 after = Math.round(after);
2193 before = Math.round(before);
2195 // align them to update_every
2196 // stretching them further away
2197 after -= after % this.data_update_every;
2198 before += this.data_update_every - (before % this.data_update_every);
2200 // the final wanted duration
2201 var wanted_duration = before - after;
2203 // to allow panning, accept just a point below our minimum
2204 if((current_duration - this.data_update_every) < min_duration)
2205 min_duration = current_duration - this.data_update_every;
2207 // we do it, but we adjust to minimum size and return false
2208 // when the wanted size is below the current and the minimum
2210 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2211 if(this.debug === true)
2212 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2214 min_duration = this.fixed_min_duration;
2216 var dt = (min_duration - wanted_duration) / 2;
2219 wanted_duration = before - after;
2223 var tolerance = this.data_update_every * 2;
2224 var movement = Math.abs(before - this.view_before);
2226 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2227 if(this.debug === true)
2228 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);
2232 if(this.current.name === 'auto') {
2233 this.log(logme + 'caller called me with mode: ' + this.current.name);
2234 this.setMode('pan');
2237 if(this.debug === true)
2238 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);
2240 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2241 this.current.force_after_ms = after;
2242 this.current.force_before_ms = before;
2243 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2247 this.legendFormatValue = function(value) {
2248 if(value === null || value === 'undefined') return '-';
2249 if(typeof value !== 'number') return value;
2251 if(this.value_decimal_detail !== -1)
2252 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2254 var abs = Math.abs(value);
2255 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2256 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2257 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2258 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2259 return (Math.round(value * 10000) / 10000).toLocaleString();
2262 this.legendSetLabelValue = function(label, value) {
2263 var series = this.element_legend_childs.series[label];
2264 if(typeof series === 'undefined') return;
2265 if(series.value === null && series.user === null) return;
2268 // this slows down firefox and edge significantly
2269 // since it requires to use innerHTML(), instead of innerText()
2271 // if the value has not changed, skip DOM update
2272 //if(series.last === value) return;
2275 if(typeof value === 'number') {
2276 var v = Math.abs(value);
2277 s = r = this.legendFormatValue(value);
2279 if(typeof series.last === 'number') {
2280 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2281 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2282 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2284 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2294 series.last = value;
2298 var s = this.legendFormatValue(value);
2300 // caching: do not update the update to show the same value again
2301 if(s === series.last_shown_value) return;
2302 series.last_shown_value = s;
2304 if(series.value !== null) series.value.innerText = s;
2305 if(series.user !== null) series.user.innerText = s;
2308 this.__legendSetDateString = function(date) {
2309 if(date !== this.__last_shown_legend_date) {
2310 this.element_legend_childs.title_date.innerText = date;
2311 this.__last_shown_legend_date = date;
2315 this.__legendSetTimeString = function(time) {
2316 if(time !== this.__last_shown_legend_time) {
2317 this.element_legend_childs.title_time.innerText = time;
2318 this.__last_shown_legend_time = time;
2322 this.__legendSetUnitsString = function(units) {
2323 if(units !== this.__last_shown_legend_units) {
2324 this.element_legend_childs.title_units.innerText = units;
2325 this.__last_shown_legend_units = units;
2329 this.legendSetDate = function(ms) {
2330 if(typeof ms !== 'number') {
2331 this.legendShowUndefined();
2335 var d = new Date(ms);
2337 if(this.element_legend_childs.title_date)
2338 this.__legendSetDateString(d.toLocaleDateString());
2340 if(this.element_legend_childs.title_time)
2341 this.__legendSetTimeString(d.toLocaleTimeString());
2343 if(this.element_legend_childs.title_units)
2344 this.__legendSetUnitsString(this.units)
2347 this.legendShowUndefined = function() {
2348 if(this.element_legend_childs.title_date)
2349 this.__legendSetDateString(' ');
2351 if(this.element_legend_childs.title_time)
2352 this.__legendSetTimeString(this.chart.name);
2354 if(this.element_legend_childs.title_units)
2355 this.__legendSetUnitsString(' ')
2357 if(this.data && this.element_legend_childs.series !== null) {
2358 var labels = this.data.dimension_names;
2359 var i = labels.length;
2361 var label = labels[i];
2363 if(typeof label === 'undefined') continue;
2364 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2365 this.legendSetLabelValue(label, null);
2370 this.legendShowLatestValues = function() {
2371 if(this.chart === null) return;
2372 if(this.selected) return;
2374 if(this.data === null || this.element_legend_childs.series === null) {
2375 this.legendShowUndefined();
2379 var show_undefined = true;
2380 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2381 show_undefined = false;
2383 if(show_undefined) {
2384 this.legendShowUndefined();
2388 this.legendSetDate(this.view_before);
2390 var labels = this.data.dimension_names;
2391 var i = labels.length;
2393 var label = labels[i];
2395 if(typeof label === 'undefined') continue;
2396 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2399 this.legendSetLabelValue(label, null);
2401 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2405 this.legendReset = function() {
2406 this.legendShowLatestValues();
2409 // this should be called just ONCE per dimension per chart
2410 this._chartDimensionColor = function(label) {
2411 if(this.colors === null) this.chartColors();
2413 if(typeof this.colors_assigned[label] === 'undefined') {
2414 if(this.colors_available.length === 0) {
2415 var len = NETDATA.themes.current.colors.length;
2417 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2420 this.colors_assigned[label] = this.colors_available.shift();
2422 if(this.debug === true)
2423 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2426 if(this.debug === true)
2427 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2430 this.colors.push(this.colors_assigned[label]);
2431 return this.colors_assigned[label];
2434 this.chartColors = function() {
2435 if(this.colors !== null) return this.colors;
2437 this.colors = new Array();
2438 this.colors_available = new Array();
2440 // add the standard colors
2441 var len = NETDATA.themes.current.colors.length;
2443 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2445 // add the user supplied colors
2446 var c = $(this.element).data('colors');
2447 // this.log('read colors: ' + c);
2448 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2449 if(typeof c !== 'string') {
2450 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2460 this.colors_available.unshift(c[len]);
2461 // this.log('adding color: ' + c[len]);
2470 this.legendUpdateDOM = function() {
2473 // check that the legend DOM is up to date for the downloaded dimensions
2474 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2475 // this.log('the legend does not have any series - requesting legend update');
2478 else if(this.data === null) {
2479 // this.log('the chart does not have any data - requesting legend update');
2482 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2486 var labels = this.data.dimension_names.toString();
2487 if(labels !== this.element_legend_childs.series.labels_key) {
2490 if(this.debug === true)
2491 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2495 if(needed === false) {
2496 // make sure colors available
2499 // do we have to update the current values?
2500 // we do this, only when the visible chart is current
2501 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2502 if(this.debug === true)
2503 this.log('chart is in latest position... updating values on legend...');
2505 //var labels = this.data.dimension_names;
2506 //var i = labels.length;
2508 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2512 if(this.colors === null) {
2513 // this is the first time we update the chart
2514 // let's assign colors to all dimensions
2515 if(this.library.track_colors() === true)
2516 for(var dim in this.chart.dimensions)
2517 this._chartDimensionColor(this.chart.dimensions[dim].name);
2519 // we will re-generate the colors for the chart
2520 // based on the selected dimensions
2523 if(this.debug === true)
2524 this.log('updating Legend DOM');
2526 // mark all dimensions as invalid
2527 this.dimensions_visibility.invalidateAll();
2529 var genLabel = function(state, parent, dim, name, count) {
2530 var color = state._chartDimensionColor(name);
2532 var user_element = null;
2533 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2534 if(user_id === null)
2535 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2536 if(user_id !== null) {
2537 user_element = document.getElementById(user_id) || null;
2538 if (user_element === null)
2539 state.log('Cannot find element with id: ' + user_id);
2542 state.element_legend_childs.series[name] = {
2543 name: document.createElement('span'),
2544 value: document.createElement('span'),
2547 last_shown_value: null
2550 var label = state.element_legend_childs.series[name];
2552 // create the dimension visibility tracking for this label
2553 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2555 var rgb = NETDATA.colorHex2Rgb(color);
2556 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2557 + state.chart.chart_type
2558 + '" style="background-color: '
2559 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2560 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2562 var text = document.createTextNode(' ' + name);
2563 label.name.appendChild(text);
2566 parent.appendChild(document.createElement('br'));
2568 parent.appendChild(label.name);
2569 parent.appendChild(label.value);
2572 var content = document.createElement('div');
2574 if(this.hasLegend()) {
2575 this.element_legend_childs = {
2577 resize_handler: document.createElement('div'),
2578 toolbox: document.createElement('div'),
2579 toolbox_left: document.createElement('div'),
2580 toolbox_right: document.createElement('div'),
2581 toolbox_reset: document.createElement('div'),
2582 toolbox_zoomin: document.createElement('div'),
2583 toolbox_zoomout: document.createElement('div'),
2584 toolbox_volume: document.createElement('div'),
2585 title_date: document.createElement('span'),
2586 title_time: document.createElement('span'),
2587 title_units: document.createElement('span'),
2588 perfect_scroller: document.createElement('div'),
2592 this.element_legend.innerHTML = '';
2594 if(this.library.toolboxPanAndZoom !== null) {
2596 function get_pan_and_zoom_step(event) {
2598 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2600 else if (event.shiftKey)
2601 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2603 else if (event.altKey)
2604 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2607 return NETDATA.options.current.pan_and_zoom_factor;
2610 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2611 this.element.appendChild(this.element_legend_childs.toolbox);
2613 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2614 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2615 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2616 this.element_legend_childs.toolbox_left.onclick = function(e) {
2619 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2620 var before = that.view_before - step;
2621 var after = that.view_after - step;
2622 if(after >= that.netdata_first)
2623 that.library.toolboxPanAndZoom(that, after, before);
2625 if(NETDATA.options.current.show_help === true)
2626 $(this.element_legend_childs.toolbox_left).popover({
2631 placement: 'bottom',
2632 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2634 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>'
2638 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2639 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2640 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2641 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2643 NETDATA.resetAllCharts(that);
2645 if(NETDATA.options.current.show_help === true)
2646 $(this.element_legend_childs.toolbox_reset).popover({
2651 placement: 'bottom',
2652 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2653 title: 'Chart Reset',
2654 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>'
2657 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2658 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2659 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2660 this.element_legend_childs.toolbox_right.onclick = function(e) {
2662 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2663 var before = that.view_before + step;
2664 var after = that.view_after + step;
2665 if(before <= that.netdata_last)
2666 that.library.toolboxPanAndZoom(that, after, before);
2668 if(NETDATA.options.current.show_help === true)
2669 $(this.element_legend_childs.toolbox_right).popover({
2674 placement: 'bottom',
2675 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2677 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>'
2681 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2682 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2683 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2684 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2686 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2687 var before = that.view_before - dt;
2688 var after = that.view_after + dt;
2689 that.library.toolboxPanAndZoom(that, after, before);
2691 if(NETDATA.options.current.show_help === true)
2692 $(this.element_legend_childs.toolbox_zoomin).popover({
2697 placement: 'bottom',
2698 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2699 title: 'Chart Zoom In',
2700 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>'
2703 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2704 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2705 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2706 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2708 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);
2709 var before = that.view_before + dt;
2710 var after = that.view_after - dt;
2712 that.library.toolboxPanAndZoom(that, after, before);
2714 if(NETDATA.options.current.show_help === true)
2715 $(this.element_legend_childs.toolbox_zoomout).popover({
2720 placement: 'bottom',
2721 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2722 title: 'Chart Zoom Out',
2723 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>'
2726 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2727 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2728 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2729 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2730 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2731 //e.preventDefault();
2732 //alert('clicked toolbox_volume on ' + that.id);
2736 this.element_legend_childs.toolbox = null;
2737 this.element_legend_childs.toolbox_left = null;
2738 this.element_legend_childs.toolbox_reset = null;
2739 this.element_legend_childs.toolbox_right = null;
2740 this.element_legend_childs.toolbox_zoomin = null;
2741 this.element_legend_childs.toolbox_zoomout = null;
2742 this.element_legend_childs.toolbox_volume = null;
2745 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2746 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2747 this.element.appendChild(this.element_legend_childs.resize_handler);
2748 if(NETDATA.options.current.show_help === true)
2749 $(this.element_legend_childs.resize_handler).popover({
2754 placement: 'bottom',
2755 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2756 title: 'Chart Resize',
2757 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>'
2761 this.element_legend_childs.resize_handler.onmousedown =
2763 that.resizeHandler(e);
2767 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2768 that.resizeHandler(e);
2771 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2772 this.element_legend.appendChild(this.element_legend_childs.title_date);
2774 this.element_legend.appendChild(document.createElement('br'));
2776 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2777 this.element_legend.appendChild(this.element_legend_childs.title_time);
2779 this.element_legend.appendChild(document.createElement('br'));
2781 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2782 this.element_legend.appendChild(this.element_legend_childs.title_units);
2784 this.element_legend.appendChild(document.createElement('br'));
2786 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2787 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2789 content.className = 'netdata-legend-series-content';
2790 this.element_legend_childs.perfect_scroller.appendChild(content);
2792 if(NETDATA.options.current.show_help === true)
2793 $(content).popover({
2798 placement: 'bottom',
2799 title: 'Chart Legend',
2800 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2801 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>'
2805 this.element_legend_childs = {
2807 resize_handler: null,
2810 toolbox_right: null,
2811 toolbox_reset: null,
2812 toolbox_zoomin: null,
2813 toolbox_zoomout: null,
2814 toolbox_volume: null,
2818 perfect_scroller: null,
2824 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2825 if(this.debug === true)
2826 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2828 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2829 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2833 var tmp = new Array();
2834 for(var dim in this.chart.dimensions) {
2835 tmp.push(this.chart.dimensions[dim].name);
2836 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2838 this.element_legend_childs.series.labels_key = tmp.toString();
2839 if(this.debug === true)
2840 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2843 // create a hidden div to be used for hidding
2844 // the original legend of the chart library
2845 var el = document.createElement('div');
2846 if(this.element_legend !== null)
2847 this.element_legend.appendChild(el);
2848 el.style.display = 'none';
2850 this.element_legend_childs.hidden = document.createElement('div');
2851 el.appendChild(this.element_legend_childs.hidden);
2853 if(this.element_legend_childs.perfect_scroller !== null) {
2854 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2856 wheelPropagation: true,
2857 swipePropagation: true,
2858 minScrollbarLength: null,
2859 maxScrollbarLength: null,
2860 useBothWheelAxes: false,
2861 suppressScrollX: true,
2862 suppressScrollY: false,
2863 scrollXMarginOffset: 0,
2864 scrollYMarginOffset: 0,
2867 Ps.update(this.element_legend_childs.perfect_scroller);
2870 this.legendShowLatestValues();
2873 this.hasLegend = function() {
2874 if(typeof this.___hasLegendCache___ !== 'undefined')
2875 return this.___hasLegendCache___;
2878 if(this.library && this.library.legend(this) === 'right-side') {
2879 var legend = $(this.element).data('legend') || 'yes';
2880 if(legend === 'yes') leg = true;
2883 this.___hasLegendCache___ = leg;
2887 this.legendWidth = function() {
2888 return (this.hasLegend())?140:0;
2891 this.legendHeight = function() {
2892 return $(this.element).height();
2895 this.chartWidth = function() {
2896 return $(this.element).width() - this.legendWidth();
2899 this.chartHeight = function() {
2900 return $(this.element).height();
2903 this.chartPixelsPerPoint = function() {
2904 // force an options provided detail
2905 var px = this.pixels_per_point;
2907 if(this.library && px < this.library.pixels_per_point(this))
2908 px = this.library.pixels_per_point(this);
2910 if(px < NETDATA.options.current.pixels_per_point)
2911 px = NETDATA.options.current.pixels_per_point;
2916 this.needsRecreation = function() {
2918 this.chart_created === true
2920 && this.library.autoresize() === false
2921 && this.tm.last_resized < NETDATA.options.last_resized
2925 this.chartURL = function() {
2926 var after, before, points_multiplier = 1;
2927 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2928 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2930 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2931 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2932 this.view_after = after * 1000;
2933 this.view_before = before * 1000;
2935 this.requested_padding = null;
2936 points_multiplier = 1;
2938 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2939 this.tm.pan_and_zoom_seq = 0;
2941 before = Math.round(this.current.force_before_ms / 1000);
2942 after = Math.round(this.current.force_after_ms / 1000);
2943 this.view_after = after * 1000;
2944 this.view_before = before * 1000;
2946 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2947 this.requested_padding = Math.round((before - after) / 2);
2948 after -= this.requested_padding;
2949 before += this.requested_padding;
2950 this.requested_padding *= 1000;
2951 points_multiplier = 2;
2954 this.current.force_before_ms = null;
2955 this.current.force_after_ms = null;
2958 this.tm.pan_and_zoom_seq = 0;
2960 before = this.before;
2962 this.view_after = after * 1000;
2963 this.view_before = before * 1000;
2965 this.requested_padding = null;
2966 points_multiplier = 1;
2969 this.requested_after = after * 1000;
2970 this.requested_before = before * 1000;
2972 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2974 // build the data URL
2975 this.data_url = this.host + this.chart.data_url;
2976 this.data_url += "&format=" + this.library.format();
2977 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2978 this.data_url += "&group=" + this.method;
2980 if(this.override_options !== null)
2981 this.data_url += "&options=" + this.override_options.toString();
2983 this.data_url += "&options=" + this.library.options(this);
2985 this.data_url += '|jsonwrap';
2987 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2988 this.data_url += '|nonzero';
2990 if(this.append_options !== null)
2991 this.data_url += '|' + this.append_options.toString();
2994 this.data_url += "&after=" + after.toString();
2997 this.data_url += "&before=" + before.toString();
3000 this.data_url += "&dimensions=" + this.dimensions;
3002 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3003 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3006 this.redrawChart = function() {
3007 if(this.data !== null)
3008 this.updateChartWithData(this.data);
3011 this.updateChartWithData = function(data) {
3012 if(this.debug === true)
3013 this.log('updateChartWithData() called.');
3015 // this may force the chart to be re-created
3019 this.updates_counter++;
3020 this.updates_since_last_unhide++;
3021 this.updates_since_last_creation++;
3023 var started = Date.now();
3025 // if the result is JSON, find the latest update-every
3026 this.data_update_every = data.view_update_every * 1000;
3027 this.data_after = data.after * 1000;
3028 this.data_before = data.before * 1000;
3029 this.netdata_first = data.first_entry * 1000;
3030 this.netdata_last = data.last_entry * 1000;
3031 this.data_points = data.points;
3034 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3035 if(this.view_after < this.data_after) {
3036 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3037 this.view_after = this.data_after;
3040 if(this.view_before > this.data_before) {
3041 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3042 this.view_before = this.data_before;
3046 this.view_after = this.data_after;
3047 this.view_before = this.data_before;
3050 if(this.debug === true) {
3051 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3053 if(this.current.force_after_ms)
3054 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3056 this.log('STATUS: forced : unset');
3058 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3059 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3060 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3061 this.log('STATUS: points : ' + (this.data_points).toString());
3064 if(this.data_points === 0) {
3069 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3070 if(this.debug === true)
3071 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3073 this.chart_created = false;
3076 // check and update the legend
3077 this.legendUpdateDOM();
3079 if(this.chart_created === true
3080 && typeof this.library.update === 'function') {
3082 if(this.debug === true)
3083 this.log('updating chart...');
3085 if(callChartLibraryUpdateSafely(data) === false)
3089 if(this.debug === true)
3090 this.log('creating chart...');
3092 if(callChartLibraryCreateSafely(data) === false)
3096 this.legendShowLatestValues();
3097 if(this.selected === true)
3098 NETDATA.globalSelectionSync.stop();
3100 // update the performance counters
3101 var now = Date.now();
3102 this.tm.last_updated = now;
3104 // don't update last_autorefreshed if this chart is
3105 // forced to be updated with global PanAndZoom
3106 if(NETDATA.globalPanAndZoom.isActive())
3107 this.tm.last_autorefreshed = 0;
3109 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3110 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3112 this.tm.last_autorefreshed = now;
3115 this.refresh_dt_ms = now - started;
3116 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3118 if(this.refresh_dt_element !== null)
3119 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3122 this.updateChart = function(callback) {
3123 if(this.debug === true)
3124 this.log('updateChart() called.');
3126 if(this._updating === true) {
3127 if(this.debug === true)
3128 this.log('I am already updating...');
3130 if(typeof callback === 'function') callback();
3134 // due to late initialization of charts and libraries
3135 // we need to check this too
3136 if(this.enabled === false) {
3137 if(this.debug === true)
3138 this.log('I am not enabled');
3140 if(typeof callback === 'function') callback();
3144 if(canBeRendered() === false) {
3145 if(typeof callback === 'function') callback();
3149 if(this.chart === null) {
3150 this.getChart(function() { that.updateChart(callback); });
3154 if(this.library.initialized === false) {
3155 if(this.library.enabled === true) {
3156 this.library.initialize(function() { that.updateChart(callback); });
3160 error('chart library "' + this.library_name + '" is not available.');
3161 if(typeof callback === 'function') callback();
3166 this.clearSelection();
3169 if(this.debug === true)
3170 this.log('updating from ' + this.data_url);
3172 NETDATA.statistics.refreshes_total++;
3173 NETDATA.statistics.refreshes_active++;
3175 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3176 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3178 this._updating = true;
3180 this.xhr = $.ajax( {
3185 'Cache-Control': 'no-cache, no-store',
3186 'Pragma': 'no-cache'
3188 xhrFields: { withCredentials: true } // required for the cookie
3190 .done(function(data) {
3191 that.xhr = undefined;
3192 that.retries_on_data_failures = 0;
3194 if(that.debug === true)
3195 that.log('data received. updating chart.');
3197 that.updateChartWithData(data);
3199 .fail(function(msg) {
3200 that.xhr = undefined;
3202 if(msg.statusText !== 'abort') {
3203 that.retries_on_data_failures++;
3204 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3205 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3206 that.retries_on_data_failures = 0;
3207 error('data download failed for url: ' + that.data_url);
3210 that.tm.last_autorefreshed = Date.now();
3211 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3215 .always(function() {
3216 that.xhr = undefined;
3218 NETDATA.statistics.refreshes_active--;
3219 that._updating = false;
3220 if(typeof callback === 'function') callback();
3226 this.isVisible = function(nocache) {
3227 if(typeof nocache === 'undefined')
3230 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3232 // caching - we do not evaluate the charts visibility
3233 // if the page has not been scrolled since the last check
3234 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3235 return this.___isVisible___;
3237 this.tm.last_visible_check = Date.now();
3239 var wh = window.innerHeight;
3240 var x = this.element.getBoundingClientRect();
3244 if(x.width === 0 || x.height === 0) {
3246 this.___isVisible___ = false;
3247 return this.___isVisible___;
3250 if(x.top < 0 && -x.top > x.height) {
3251 // the chart is entirely above
3252 ret = -x.top - x.height;
3254 else if(x.top > wh) {
3255 // the chart is entirely below
3259 if(ret > tolerance) {
3260 // the chart is too far
3263 this.___isVisible___ = false;
3264 return this.___isVisible___;
3267 // the chart is inside or very close
3270 this.___isVisible___ = true;
3271 return this.___isVisible___;
3275 this.isAutoRefreshable = function() {
3276 return (this.current.autorefresh);
3279 this.canBeAutoRefreshed = function() {
3280 var now = Date.now();
3282 if(this.running === true) {
3283 if(this.debug === true)
3284 this.log('I am already running');
3289 if(this.enabled === false) {
3290 if(this.debug === true)
3291 this.log('I am not enabled');
3296 if(this.library === null || this.library.enabled === false) {
3297 error('charting library "' + this.library_name + '" is not available');
3298 if(this.debug === true)
3299 this.log('My chart library ' + this.library_name + ' is not available');
3304 if(this.isVisible() === false) {
3305 if(NETDATA.options.debug.visibility === true || this.debug === true)
3306 this.log('I am not visible');
3311 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3312 if(this.debug === true)
3313 this.log('timed force update detected - allowing this update');
3315 this.current.force_update_at = 0;
3319 if(this.isAutoRefreshable() === true) {
3320 // allow the first update, even if the page is not visible
3321 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3322 if(NETDATA.options.debug.focus === true || this.debug === true)
3323 this.log('canBeAutoRefreshed(): page does not have focus');
3328 if(this.needsRecreation() === true) {
3329 if(this.debug === true)
3330 this.log('canBeAutoRefreshed(): needs re-creation.');
3335 // options valid only for autoRefresh()
3336 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3337 if(NETDATA.globalPanAndZoom.isActive()) {
3338 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3339 if(this.debug === true)
3340 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3345 if(this.debug === true)
3346 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3352 if(this.selected === true) {
3353 if(this.debug === true)
3354 this.log('canBeAutoRefreshed(): I have a selection in place.');
3359 if(this.paused === true) {
3360 if(this.debug === true)
3361 this.log('canBeAutoRefreshed(): I am paused.');
3366 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3367 if(this.debug === true)
3368 this.log('canBeAutoRefreshed(): It is time to update me.');
3378 this.autoRefresh = function(callback) {
3379 if(this.canBeAutoRefreshed() === true && this.running === false) {
3382 state.running = true;
3383 state.updateChart(function() {
3384 state.running = false;
3386 if(typeof callback !== 'undefined')
3391 if(typeof callback !== 'undefined')
3396 this._defaultsFromDownloadedChart = function(chart) {
3398 this.chart_url = chart.url;
3399 this.data_update_every = chart.update_every * 1000;
3400 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3401 this.tm.last_info_downloaded = Date.now();
3403 if(this.title === null)
3404 this.title = chart.title;
3406 if(this.units === null)
3407 this.units = chart.units;
3410 // fetch the chart description from the netdata server
3411 this.getChart = function(callback) {
3412 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3414 this._defaultsFromDownloadedChart(this.chart);
3415 if(typeof callback === 'function') callback();
3418 this.chart_url = "/api/v1/chart?chart=" + this.id;
3420 if(this.debug === true)
3421 this.log('downloading ' + this.chart_url);
3424 url: this.host + this.chart_url,
3427 xhrFields: { withCredentials: true } // required for the cookie
3429 .done(function(chart) {
3430 chart.url = that.chart_url;
3431 that._defaultsFromDownloadedChart(chart);
3432 NETDATA.chartRegistry.add(that.host, that.id, chart);
3435 NETDATA.error(404, that.chart_url);
3436 error('chart not found on url "' + that.chart_url + '"');
3438 .always(function() {
3439 if(typeof callback === 'function') callback();
3444 // ============================================================================================================
3450 NETDATA.resetAllCharts = function(state) {
3451 // first clear the global selection sync
3452 // to make sure no chart is in selected state
3453 state.globalSelectionSyncStop();
3455 // there are 2 possibilities here
3456 // a. state is the global Pan and Zoom master
3457 // b. state is not the global Pan and Zoom master
3459 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3462 // clear the global Pan and Zoom
3463 // this will also refresh the master
3464 // and unblock any charts currently mirroring the master
3465 NETDATA.globalPanAndZoom.clearMaster();
3467 // if we were not the master, reset our status too
3468 // this is required because most probably the mouse
3469 // is over this chart, blocking it from auto-refreshing
3470 if(master === false && (state.paused === true || state.selected === true))
3474 // get or create a chart state, given a DOM element
3475 NETDATA.chartState = function(element) {
3476 var state = $(element).data('netdata-state-object') || null;
3477 if(state === null) {
3478 state = new chartState(element);
3479 $(element).data('netdata-state-object', state);
3484 // ----------------------------------------------------------------------------------------------------------------
3485 // Library functions
3487 // Load a script without jquery
3488 // This is used to load jquery - after it is loaded, we use jquery
3489 NETDATA._loadjQuery = function(callback) {
3490 if(typeof jQuery === 'undefined') {
3491 if(NETDATA.options.debug.main_loop === true)
3492 console.log('loading ' + NETDATA.jQuery);
3494 var script = document.createElement('script');
3495 script.type = 'text/javascript';
3496 script.async = true;
3497 script.src = NETDATA.jQuery;
3499 // script.onabort = onError;
3500 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3501 if(typeof callback === "function")
3502 script.onload = callback;
3504 var s = document.getElementsByTagName('script')[0];
3505 s.parentNode.insertBefore(script, s);
3507 else if(typeof callback === "function")
3511 NETDATA._loadCSS = function(filename) {
3512 // don't use jQuery here
3513 // styles are loaded before jQuery
3514 // to eliminate showing an unstyled page to the user
3516 var fileref = document.createElement("link");
3517 fileref.setAttribute("rel", "stylesheet");
3518 fileref.setAttribute("type", "text/css");
3519 fileref.setAttribute("href", filename);
3521 if (typeof fileref !== 'undefined')
3522 document.getElementsByTagName("head")[0].appendChild(fileref);
3525 NETDATA.colorHex2Rgb = function(hex) {
3526 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3527 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3528 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3529 return r + r + g + g + b + b;
3532 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3534 r: parseInt(result[1], 16),
3535 g: parseInt(result[2], 16),
3536 b: parseInt(result[3], 16)
3540 NETDATA.colorLuminance = function(hex, lum) {
3541 // validate hex string
3542 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3544 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3548 // convert to decimal and change luminosity
3549 var rgb = "#", c, i;
3550 for (i = 0; i < 3; i++) {
3551 c = parseInt(hex.substr(i*2,2), 16);
3552 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3553 rgb += ("00"+c).substr(c.length);
3559 NETDATA.guid = function() {
3561 return Math.floor((1 + Math.random()) * 0x10000)
3566 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3569 NETDATA.zeropad = function(x) {
3570 if(x > -10 && x < 10) return '0' + x.toString();
3571 else return x.toString();
3574 // user function to signal us the DOM has been
3576 NETDATA.updatedDom = function() {
3577 NETDATA.options.updated_dom = true;
3580 NETDATA.ready = function(callback) {
3581 NETDATA.options.pauseCallback = callback;
3584 NETDATA.pause = function(callback) {
3585 if(NETDATA.options.pause === true)
3588 NETDATA.options.pauseCallback = callback;
3591 NETDATA.unpause = function() {
3592 NETDATA.options.pauseCallback = null;
3593 NETDATA.options.updated_dom = true;
3594 NETDATA.options.pause = false;
3597 // ----------------------------------------------------------------------------------------------------------------
3599 // this is purely sequencial charts refresher
3600 // it is meant to be autonomous
3601 NETDATA.chartRefresherNoParallel = function(index) {
3602 if(NETDATA.options.debug.mail_loop === true)
3603 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3605 if(NETDATA.options.updated_dom === true) {
3606 // the dom has been updated
3607 // get the dom parts again
3608 NETDATA.parseDom(NETDATA.chartRefresher);
3611 if(index >= NETDATA.options.targets.length) {
3612 if(NETDATA.options.debug.main_loop === true)
3613 console.log('waiting to restart main loop...');
3615 NETDATA.options.auto_refresher_fast_weight = 0;
3617 setTimeout(function() {
3618 NETDATA.chartRefresher();
3619 }, NETDATA.options.current.idle_between_loops);
3622 var state = NETDATA.options.targets[index];
3624 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3625 if(NETDATA.options.debug.main_loop === true)
3626 console.log('fast rendering...');
3628 state.autoRefresh(function() {
3629 NETDATA.chartRefresherNoParallel(++index);
3633 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3634 NETDATA.options.auto_refresher_fast_weight = 0;
3636 setTimeout(function() {
3637 state.autoRefresh(function() {
3638 NETDATA.chartRefresherNoParallel(++index);
3640 }, NETDATA.options.current.idle_between_charts);
3645 // this is part of the parallel refresher
3646 // its cause is to refresh sequencially all the charts
3647 // that depend on chart library initialization
3648 // it will call the parallel refresher back
3649 // as soon as it sees a chart that its chart library
3651 NETDATA.chartRefresher_uninitialized = function() {
3652 if(NETDATA.options.updated_dom === true) {
3653 // the dom has been updated
3654 // get the dom parts again
3655 NETDATA.parseDom(NETDATA.chartRefresher);
3659 if(NETDATA.options.sequencial.length === 0)
3660 NETDATA.chartRefresher();
3662 var state = NETDATA.options.sequencial.pop();
3663 if(state.library.initialized === true)
3664 NETDATA.chartRefresher();
3666 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3670 NETDATA.chartRefresherWaitTime = function() {
3671 return NETDATA.options.current.idle_parallel_loops;
3674 // the default refresher
3675 // it will create 2 sets of charts:
3676 // - the ones that can be refreshed in parallel
3677 // - the ones that depend on something else
3678 // the first set will be executed in parallel
3679 // the second will be given to NETDATA.chartRefresher_uninitialized()
3680 NETDATA.chartRefresher = function() {
3681 // console.log('auto-refresher...');
3683 if(NETDATA.options.pause === true) {
3684 // console.log('auto-refresher is paused');
3685 setTimeout(NETDATA.chartRefresher,
3686 NETDATA.chartRefresherWaitTime());
3690 if(typeof NETDATA.options.pauseCallback === 'function') {
3691 // console.log('auto-refresher is calling pauseCallback');
3692 NETDATA.options.pause = true;
3693 NETDATA.options.pauseCallback();
3694 NETDATA.chartRefresher();
3698 if(NETDATA.options.current.parallel_refresher === false) {
3699 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3700 NETDATA.chartRefresherNoParallel(0);
3704 if(NETDATA.options.updated_dom === true) {
3705 // the dom has been updated
3706 // get the dom parts again
3707 // console.log('auto-refresher is calling parseDom()');
3708 NETDATA.parseDom(NETDATA.chartRefresher);
3712 var parallel = new Array();
3713 var targets = NETDATA.options.targets;
3714 var len = targets.length;
3717 state = targets[len];
3718 if(state.isVisible() === false || state.running === true)
3721 if(state.library.initialized === false) {
3722 if(state.library.enabled === true) {
3723 state.library.initialize(NETDATA.chartRefresher);
3727 state.error('chart library "' + state.library_name + '" is not enabled.');
3731 parallel.unshift(state);
3734 if(parallel.length > 0) {
3735 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3736 // this will execute the jobs in parallel
3737 $(parallel).each(function() {
3742 // console.log('auto-refresher nothing to do');
3745 // run the next refresh iteration
3746 setTimeout(NETDATA.chartRefresher,
3747 NETDATA.chartRefresherWaitTime());
3750 NETDATA.parseDom = function(callback) {
3751 NETDATA.options.last_page_scroll = Date.now();
3752 NETDATA.options.updated_dom = false;
3754 var targets = $('div[data-netdata]'); //.filter(':visible');
3756 if(NETDATA.options.debug.main_loop === true)
3757 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3759 NETDATA.options.targets = new Array();
3760 var len = targets.length;
3762 // the initialization will take care of sizing
3763 // and the "loading..." message
3764 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3767 if(typeof callback === 'function') callback();
3770 // this is the main function - where everything starts
3771 NETDATA.start = function() {
3772 // this should be called only once
3774 NETDATA.options.page_is_visible = true;
3776 $(window).blur(function() {
3777 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3778 NETDATA.options.page_is_visible = false;
3779 if(NETDATA.options.debug.focus === true)
3780 console.log('Lost Focus!');
3784 $(window).focus(function() {
3785 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3786 NETDATA.options.page_is_visible = true;
3787 if(NETDATA.options.debug.focus === true)
3788 console.log('Focus restored!');
3792 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3793 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3794 NETDATA.options.page_is_visible = false;
3795 if(NETDATA.options.debug.focus === true)
3796 console.log('Document has no focus!');
3800 // bootstrap tab switching
3801 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3803 // bootstrap modal switching
3804 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3805 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3807 // bootstrap collapse switching
3808 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3809 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3811 NETDATA.parseDom(NETDATA.chartRefresher);
3813 // Alarms initialization
3814 setTimeout(NETDATA.alarms.init, 1000);
3816 // Registry initialization
3817 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3819 if(typeof netdataCallback === 'function')
3823 // ----------------------------------------------------------------------------------------------------------------
3826 NETDATA.peityInitialize = function(callback) {
3827 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3829 url: NETDATA.peity_js,
3832 xhrFields: { withCredentials: true } // required for the cookie
3835 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3838 NETDATA.chartLibraries.peity.enabled = false;
3839 NETDATA.error(100, NETDATA.peity_js);
3841 .always(function() {
3842 if(typeof callback === "function")
3847 NETDATA.chartLibraries.peity.enabled = false;
3848 if(typeof callback === "function")
3853 NETDATA.peityChartUpdate = function(state, data) {
3854 state.peity_instance.innerHTML = data.result;
3856 if(state.peity_options.stroke !== state.chartColors()[0]) {
3857 state.peity_options.stroke = state.chartColors()[0];
3858 if(state.chart.chart_type === 'line')
3859 state.peity_options.fill = NETDATA.themes.current.background;
3861 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3864 $(state.peity_instance).peity('line', state.peity_options);
3868 NETDATA.peityChartCreate = function(state, data) {
3869 state.peity_instance = document.createElement('div');
3870 state.element_chart.appendChild(state.peity_instance);
3872 var self = $(state.element);
3873 state.peity_options = {
3874 stroke: NETDATA.themes.current.foreground,
3875 strokeWidth: self.data('peity-strokewidth') || 1,
3876 width: state.chartWidth(),
3877 height: state.chartHeight(),
3878 fill: NETDATA.themes.current.foreground
3881 NETDATA.peityChartUpdate(state, data);
3885 // ----------------------------------------------------------------------------------------------------------------
3888 NETDATA.sparklineInitialize = function(callback) {
3889 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3891 url: NETDATA.sparkline_js,
3894 xhrFields: { withCredentials: true } // required for the cookie
3897 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3900 NETDATA.chartLibraries.sparkline.enabled = false;
3901 NETDATA.error(100, NETDATA.sparkline_js);
3903 .always(function() {
3904 if(typeof callback === "function")
3909 NETDATA.chartLibraries.sparkline.enabled = false;
3910 if(typeof callback === "function")
3915 NETDATA.sparklineChartUpdate = function(state, data) {
3916 state.sparkline_options.width = state.chartWidth();
3917 state.sparkline_options.height = state.chartHeight();
3919 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3923 NETDATA.sparklineChartCreate = function(state, data) {
3924 var self = $(state.element);
3925 var type = self.data('sparkline-type') || 'line';
3926 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3927 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3928 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3929 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3930 var composite = self.data('sparkline-composite') || undefined;
3931 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3932 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3933 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3934 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3935 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3936 var spotColor = self.data('sparkline-spotcolor') || undefined;
3937 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3938 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3939 var spotRadius = self.data('sparkline-spotradius') || undefined;
3940 var valueSpots = self.data('sparkline-valuespots') || undefined;
3941 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3942 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3943 var lineWidth = self.data('sparkline-linewidth') || undefined;
3944 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3945 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3946 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3947 var xvalues = self.data('sparkline-xvalues') || undefined;
3948 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3949 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3950 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3951 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3952 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3953 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3954 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3955 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3956 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3957 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3958 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3959 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3960 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3961 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3962 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3963 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3964 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3965 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3966 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3967 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3968 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3969 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3971 if(spotColor === 'disable') spotColor='';
3972 if(minSpotColor === 'disable') minSpotColor='';
3973 if(maxSpotColor === 'disable') maxSpotColor='';
3975 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3977 state.sparkline_options = {
3979 lineColor: lineColor,
3980 fillColor: fillColor,
3981 chartRangeMin: chartRangeMin,
3982 chartRangeMax: chartRangeMax,
3983 composite: composite,
3984 enableTagOptions: enableTagOptions,
3985 tagOptionPrefix: tagOptionPrefix,
3986 tagValuesAttribute: tagValuesAttribute,
3987 disableHiddenCheck: disableHiddenCheck,
3988 defaultPixelsPerValue: defaultPixelsPerValue,
3989 spotColor: spotColor,
3990 minSpotColor: minSpotColor,
3991 maxSpotColor: maxSpotColor,
3992 spotRadius: spotRadius,
3993 valueSpots: valueSpots,
3994 highlightSpotColor: highlightSpotColor,
3995 highlightLineColor: highlightLineColor,
3996 lineWidth: lineWidth,
3997 normalRangeMin: normalRangeMin,
3998 normalRangeMax: normalRangeMax,
3999 drawNormalOnTop: drawNormalOnTop,
4001 chartRangeClip: chartRangeClip,
4002 chartRangeMinX: chartRangeMinX,
4003 chartRangeMaxX: chartRangeMaxX,
4004 disableInteraction: disableInteraction,
4005 disableTooltips: disableTooltips,
4006 disableHighlight: disableHighlight,
4007 highlightLighten: highlightLighten,
4008 highlightColor: highlightColor,
4009 tooltipContainer: tooltipContainer,
4010 tooltipClassname: tooltipClassname,
4011 tooltipChartTitle: state.title,
4012 tooltipFormat: tooltipFormat,
4013 tooltipPrefix: tooltipPrefix,
4014 tooltipSuffix: tooltipSuffix,
4015 tooltipSkipNull: tooltipSkipNull,
4016 tooltipValueLookups: tooltipValueLookups,
4017 tooltipFormatFieldlist: tooltipFormatFieldlist,
4018 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4019 numberFormatter: numberFormatter,
4020 numberDigitGroupSep: numberDigitGroupSep,
4021 numberDecimalMark: numberDecimalMark,
4022 numberDigitGroupCount: numberDigitGroupCount,
4023 animatedZooms: animatedZooms,
4024 width: state.chartWidth(),
4025 height: state.chartHeight()
4028 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4032 // ----------------------------------------------------------------------------------------------------------------
4039 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4040 if(after < state.netdata_first)
4041 after = state.netdata_first;
4043 if(before > state.netdata_last)
4044 before = state.netdata_last;
4046 state.setMode('zoom');
4047 state.globalSelectionSyncStop();
4048 state.globalSelectionSyncDelay();
4049 state.dygraph_user_action = true;
4050 state.dygraph_force_zoom = true;
4051 state.updateChartPanOrZoom(after, before);
4052 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4055 NETDATA.dygraphSetSelection = function(state, t) {
4056 if(typeof state.dygraph_instance !== 'undefined') {
4057 var r = state.calculateRowForTime(t);
4059 state.dygraph_instance.setSelection(r);
4061 state.dygraph_instance.clearSelection();
4062 state.legendShowUndefined();
4069 NETDATA.dygraphClearSelection = function(state, t) {
4070 if(typeof state.dygraph_instance !== 'undefined') {
4071 state.dygraph_instance.clearSelection();
4076 NETDATA.dygraphSmoothInitialize = function(callback) {
4078 url: NETDATA.dygraph_smooth_js,
4081 xhrFields: { withCredentials: true } // required for the cookie
4084 NETDATA.dygraph.smooth = true;
4085 smoothPlotter.smoothing = 0.3;
4088 NETDATA.dygraph.smooth = false;
4090 .always(function() {
4091 if(typeof callback === "function")
4096 NETDATA.dygraphInitialize = function(callback) {
4097 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4099 url: NETDATA.dygraph_js,
4102 xhrFields: { withCredentials: true } // required for the cookie
4105 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4108 NETDATA.chartLibraries.dygraph.enabled = false;
4109 NETDATA.error(100, NETDATA.dygraph_js);
4111 .always(function() {
4112 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4113 NETDATA.dygraphSmoothInitialize(callback);
4114 else if(typeof callback === "function")
4119 NETDATA.chartLibraries.dygraph.enabled = false;
4120 if(typeof callback === "function")
4125 NETDATA.dygraphChartUpdate = function(state, data) {
4126 var dygraph = state.dygraph_instance;
4128 if(typeof dygraph === 'undefined')
4129 return NETDATA.dygraphChartCreate(state, data);
4131 // when the chart is not visible, and hidden
4132 // if there is a window resize, dygraph detects
4133 // its element size as 0x0.
4134 // this will make it re-appear properly
4136 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4140 file: data.result.data,
4141 colors: state.chartColors(),
4142 labels: data.result.labels,
4143 labelsDivWidth: state.chartWidth() - 70,
4144 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4147 if(state.dygraph_force_zoom === true) {
4148 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4149 state.log('dygraphChartUpdate() forced zoom update');
4151 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4152 options.isZoomedIgnoreProgrammaticZoom = true;
4153 state.dygraph_force_zoom = false;
4155 else if(state.current.name !== 'auto') {
4156 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4157 state.log('dygraphChartUpdate() loose update');
4160 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4161 state.log('dygraphChartUpdate() strict update');
4163 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4164 options.isZoomedIgnoreProgrammaticZoom = true;
4167 options.valueRange = state.dygraph_options.valueRange;
4169 var oldMax = null, oldMin = null;
4170 if(state.__commonMin !== null) {
4171 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4172 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4174 if(state.__commonMax !== null) {
4175 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4176 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4179 if(state.dygraph_smooth_eligible === true) {
4180 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4181 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4182 NETDATA.dygraphChartCreate(state, data);
4187 dygraph.updateOptions(options);
4190 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4191 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4192 options.valueRange[0] = NETDATA.commonMin.get(state);
4195 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4196 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4197 options.valueRange[1] = NETDATA.commonMax.get(state);
4201 if(redraw === true) {
4202 // state.log('forcing redraw to adapt to common- min/max');
4203 dygraph.updateOptions(options);
4206 state.dygraph_last_rendered = Date.now();
4210 NETDATA.dygraphChartCreate = function(state, data) {
4211 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4212 state.log('dygraphChartCreate()');
4214 var self = $(state.element);
4216 var chart_type = state.chart.chart_type;
4217 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4218 chart_type = self.data('dygraph-type') || chart_type;
4220 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4221 smooth = self.data('dygraph-smooth') || smooth;
4223 if(NETDATA.dygraph.smooth === false)
4226 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4227 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4229 state.dygraph_options = {
4230 colors: self.data('dygraph-colors') || state.chartColors(),
4232 // leave a few pixels empty on the right of the chart
4233 rightGap: self.data('dygraph-rightgap') || 5,
4234 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4235 showRoller: self.data('dygraph-showroller') || false,
4237 title: self.data('dygraph-title') || state.title,
4238 titleHeight: self.data('dygraph-titleheight') || 19,
4240 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4241 labels: data.result.labels,
4242 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4243 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4244 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4245 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4246 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4249 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4250 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4252 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4253 xRangePad: self.data('dygraph-xrangepad') || 0,
4254 yRangePad: self.data('dygraph-yrangepad') || 1,
4256 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4258 ylabel: state.units,
4259 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4261 // the function to plot the chart
4264 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4265 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4266 strokePattern: self.data('dygraph-strokepattern') || undefined,
4268 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4269 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4270 drawPoints: self.data('dygraph-drawpoints') || false,
4272 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4273 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4275 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4276 pointSize: self.data('dygraph-pointsize') || 1,
4278 // enabling this makes the chart with little square lines
4279 stepPlot: self.data('dygraph-stepplot') || false,
4281 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4282 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4283 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4285 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4286 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4287 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4288 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4290 drawAxis: self.data('dygraph-drawaxis') || true,
4291 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4292 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4293 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4295 drawGrid: self.data('dygraph-drawgrid') || true,
4296 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4297 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4298 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4300 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4301 sigFigs: self.data('dygraph-sigfigs') || null,
4302 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4303 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4305 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4306 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4307 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4309 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4310 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4314 ticker: Dygraph.dateTicker,
4315 axisLabelFormatter: function (d, gran) {
4316 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4318 valueFormatter: function (ms) {
4319 //var d = new Date(ms);
4320 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4322 // no need to return anything here
4329 valueFormatter: function (x) {
4330 // we format legends with the state object
4331 // no need to do anything here
4332 // return (Math.round(x*100) / 100).toLocaleString();
4333 // return state.legendFormatValue(x);
4338 legendFormatter: function(data) {
4339 var elements = state.element_legend_childs;
4341 // if the hidden div is not there
4342 // we are not managing the legend
4343 if(elements.hidden === null) return;
4345 if (typeof data.x !== 'undefined') {
4346 state.legendSetDate(data.x);
4347 var i = data.series.length;
4349 var series = data.series[i];
4350 if(series.isVisible === true)
4351 state.legendSetLabelValue(series.label, series.y);
4357 drawCallback: function(dygraph, is_initial) {
4358 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4359 state.dygraph_user_action = false;
4361 var x_range = dygraph.xAxisRange();
4362 var after = Math.round(x_range[0]);
4363 var before = Math.round(x_range[1]);
4365 if(NETDATA.options.debug.dygraph === true)
4366 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4368 if(before <= state.netdata_last && after >= state.netdata_first)
4369 state.updateChartPanOrZoom(after, before);
4372 zoomCallback: function(minDate, maxDate, yRanges) {
4373 if(NETDATA.options.debug.dygraph === true)
4374 state.log('dygraphZoomCallback()');
4376 state.globalSelectionSyncStop();
4377 state.globalSelectionSyncDelay();
4378 state.setMode('zoom');
4380 // refresh it to the greatest possible zoom level
4381 state.dygraph_user_action = true;
4382 state.dygraph_force_zoom = true;
4383 state.updateChartPanOrZoom(minDate, maxDate);
4385 highlightCallback: function(event, x, points, row, seriesName) {
4386 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4387 state.log('dygraphHighlightCallback()');
4391 // there is a bug in dygraph when the chart is zoomed enough
4392 // the time it thinks is selected is wrong
4393 // here we calculate the time t based on the row number selected
4395 var t = state.data_after + row * state.data_update_every;
4396 // 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);
4398 state.globalSelectionSync(x);
4400 // fix legend zIndex using the internal structures of dygraph legend module
4401 // this works, but it is a hack!
4402 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4404 unhighlightCallback: function(event) {
4405 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4406 state.log('dygraphUnhighlightCallback()');
4408 state.unpauseChart();
4409 state.globalSelectionSyncStop();
4411 interactionModel : {
4412 mousedown: function(event, dygraph, context) {
4413 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4414 state.log('interactionModel.mousedown()');
4416 state.dygraph_user_action = true;
4417 state.globalSelectionSyncStop();
4419 if(NETDATA.options.debug.dygraph === true)
4420 state.log('dygraphMouseDown()');
4422 // Right-click should not initiate a zoom.
4423 if(event.button && event.button === 2) return;
4425 context.initializeMouseDown(event, dygraph, context);
4427 if(event.button && event.button === 1) {
4428 if (event.altKey || event.shiftKey) {
4429 state.setMode('pan');
4430 state.globalSelectionSyncDelay();
4431 Dygraph.startPan(event, dygraph, context);
4434 state.setMode('zoom');
4435 state.globalSelectionSyncDelay();
4436 Dygraph.startZoom(event, dygraph, context);
4440 if (event.altKey || event.shiftKey) {
4441 state.setMode('zoom');
4442 state.globalSelectionSyncDelay();
4443 Dygraph.startZoom(event, dygraph, context);
4446 state.setMode('pan');
4447 state.globalSelectionSyncDelay();
4448 Dygraph.startPan(event, dygraph, context);
4452 mousemove: function(event, dygraph, context) {
4453 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4454 state.log('interactionModel.mousemove()');
4456 if(context.isPanning) {
4457 state.dygraph_user_action = true;
4458 state.globalSelectionSyncStop();
4459 state.globalSelectionSyncDelay();
4460 state.setMode('pan');
4461 context.is2DPan = false;
4462 Dygraph.movePan(event, dygraph, context);
4464 else if(context.isZooming) {
4465 state.dygraph_user_action = true;
4466 state.globalSelectionSyncStop();
4467 state.globalSelectionSyncDelay();
4468 state.setMode('zoom');
4469 Dygraph.moveZoom(event, dygraph, context);
4472 mouseup: function(event, dygraph, context) {
4473 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4474 state.log('interactionModel.mouseup()');
4476 if (context.isPanning) {
4477 state.dygraph_user_action = true;
4478 state.globalSelectionSyncDelay();
4479 Dygraph.endPan(event, dygraph, context);
4481 else if (context.isZooming) {
4482 state.dygraph_user_action = true;
4483 state.globalSelectionSyncDelay();
4484 Dygraph.endZoom(event, dygraph, context);
4487 click: function(event, dygraph, context) {
4488 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4489 state.log('interactionModel.click()');
4491 event.preventDefault();
4493 dblclick: function(event, dygraph, context) {
4494 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4495 state.log('interactionModel.dblclick()');
4496 NETDATA.resetAllCharts(state);
4498 wheel: function(event, dygraph, context) {
4499 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500 state.log('interactionModel.wheel()');
4502 // Take the offset of a mouse event on the dygraph canvas and
4503 // convert it to a pair of percentages from the bottom left.
4504 // (Not top left, bottom is where the lower value is.)
4505 function offsetToPercentage(g, offsetX, offsetY) {
4506 // This is calculating the pixel offset of the leftmost date.
4507 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4508 var yar0 = g.yAxisRange(0);
4510 // This is calculating the pixel of the higest value. (Top pixel)
4511 var yOffset = g.toDomCoords(null, yar0[1])[1];
4513 // x y w and h are relative to the corner of the drawing area,
4514 // so that the upper corner of the drawing area is (0, 0).
4515 var x = offsetX - xOffset;
4516 var y = offsetY - yOffset;
4518 // This is computing the rightmost pixel, effectively defining the
4520 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4522 // This is computing the lowest pixel, effectively defining the height.
4523 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4525 // Percentage from the left.
4526 var xPct = w === 0 ? 0 : (x / w);
4527 // Percentage from the top.
4528 var yPct = h === 0 ? 0 : (y / h);
4530 // The (1-) part below changes it from "% distance down from the top"
4531 // to "% distance up from the bottom".
4532 return [xPct, (1-yPct)];
4535 // Adjusts [x, y] toward each other by zoomInPercentage%
4536 // Split it so the left/bottom axis gets xBias/yBias of that change and
4537 // tight/top gets (1-xBias)/(1-yBias) of that change.
4539 // If a bias is missing it splits it down the middle.
4540 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4541 xBias = xBias || 0.5;
4542 yBias = yBias || 0.5;
4544 function adjustAxis(axis, zoomInPercentage, bias) {
4545 var delta = axis[1] - axis[0];
4546 var increment = delta * zoomInPercentage;
4547 var foo = [increment * bias, increment * (1-bias)];
4549 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4552 var yAxes = g.yAxisRanges();
4554 for (var i = 0; i < yAxes.length; i++) {
4555 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4558 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4561 if(event.altKey || event.shiftKey) {
4562 state.dygraph_user_action = true;
4564 state.globalSelectionSyncStop();
4565 state.globalSelectionSyncDelay();
4567 // http://dygraphs.com/gallery/interaction-api.js
4569 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4571 normal_def = event.wheelDelta / 40;
4574 normal_def = event.deltaY * -1.2;
4576 var normal = (event.detail) ? event.detail * -1 : normal_def;
4577 var percentage = normal / 50;
4579 if (!(event.offsetX && event.offsetY)){
4580 event.offsetX = event.layerX - event.target.offsetLeft;
4581 event.offsetY = event.layerY - event.target.offsetTop;
4584 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4585 var xPct = percentages[0];
4586 var yPct = percentages[1];
4588 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4589 var after = new_x_range[0];
4590 var before = new_x_range[1];
4592 var first = state.netdata_first + state.data_update_every;
4593 var last = state.netdata_last + state.data_update_every;
4596 after -= (before - last);
4603 state.setMode('zoom');
4604 if(state.updateChartPanOrZoom(after, before) === true)
4605 dygraph.updateOptions({ dateWindow: [ after, before ] });
4607 event.preventDefault();
4610 touchstart: function(event, dygraph, context) {
4611 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4612 state.log('interactionModel.touchstart()');
4614 state.dygraph_user_action = true;
4615 state.setMode('zoom');
4618 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4620 // we overwrite the touch directions at the end, to overwrite
4621 // the internal default of dygraphs
4622 context.touchDirections = { x: true, y: false };
4624 state.dygraph_last_touch_start = Date.now();
4625 state.dygraph_last_touch_move = 0;
4627 if(typeof event.touches[0].pageX === 'number')
4628 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4630 state.dygraph_last_touch_page_x = 0;
4632 touchmove: function(event, dygraph, context) {
4633 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4634 state.log('interactionModel.touchmove()');
4636 state.dygraph_user_action = true;
4637 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4639 state.dygraph_last_touch_move = Date.now();
4641 touchend: function(event, dygraph, context) {
4642 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4643 state.log('interactionModel.touchend()');
4645 state.dygraph_user_action = true;
4646 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4648 // if it didn't move, it is a selection
4649 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4650 // internal api of dygraphs
4651 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4652 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4653 if(NETDATA.dygraphSetSelection(state, t) === true)
4654 state.globalSelectionSync(t);
4657 // if it was double tap within double click time, reset the charts
4658 var now = Date.now();
4659 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4660 if(state.dygraph_last_touch_move === 0) {
4661 var dt = now - state.dygraph_last_touch_end;
4662 if(dt <= NETDATA.options.current.double_click_speed)
4663 NETDATA.resetAllCharts(state);
4667 // remember the timestamp of the last touch end
4668 state.dygraph_last_touch_end = now;
4673 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4674 state.dygraph_options.drawGrid = false;
4675 state.dygraph_options.drawAxis = false;
4676 state.dygraph_options.title = undefined;
4677 state.dygraph_options.ylabel = undefined;
4678 state.dygraph_options.yLabelWidth = 0;
4679 state.dygraph_options.labelsDivWidth = 120;
4680 state.dygraph_options.labelsDivStyles.width = '120px';
4681 state.dygraph_options.labelsSeparateLines = true;
4682 state.dygraph_options.rightGap = 0;
4683 state.dygraph_options.yRangePad = 1;
4686 if(smooth === true) {
4687 state.dygraph_smooth_eligible = true;
4689 if(NETDATA.options.current.smooth_plot === true)
4690 state.dygraph_options.plotter = smoothPlotter;
4692 else state.dygraph_smooth_eligible = false;
4694 state.dygraph_instance = new Dygraph(state.element_chart,
4695 data.result.data, state.dygraph_options);
4697 state.dygraph_force_zoom = false;
4698 state.dygraph_user_action = false;
4699 state.dygraph_last_rendered = Date.now();
4701 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4702 state.__commonMin = self.data('common-min') || null;
4703 state.__commonMax = self.data('common-max') || null;
4706 state.log('incompatible version of dygraphs detected');
4707 state.__commonMin = null;
4708 state.__commonMax = null;
4714 // ----------------------------------------------------------------------------------------------------------------
4717 NETDATA.morrisInitialize = function(callback) {
4718 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4720 // morris requires raphael
4721 if(!NETDATA.chartLibraries.raphael.initialized) {
4722 if(NETDATA.chartLibraries.raphael.enabled) {
4723 NETDATA.raphaelInitialize(function() {
4724 NETDATA.morrisInitialize(callback);
4728 NETDATA.chartLibraries.morris.enabled = false;
4729 if(typeof callback === "function")
4734 NETDATA._loadCSS(NETDATA.morris_css);
4737 url: NETDATA.morris_js,
4740 xhrFields: { withCredentials: true } // required for the cookie
4743 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4746 NETDATA.chartLibraries.morris.enabled = false;
4747 NETDATA.error(100, NETDATA.morris_js);
4749 .always(function() {
4750 if(typeof callback === "function")
4756 NETDATA.chartLibraries.morris.enabled = false;
4757 if(typeof callback === "function")
4762 NETDATA.morrisChartUpdate = function(state, data) {
4763 state.morris_instance.setData(data.result.data);
4767 NETDATA.morrisChartCreate = function(state, data) {
4769 state.morris_options = {
4770 element: state.element_chart.id,
4771 data: data.result.data,
4773 ykeys: data.dimension_names,
4774 labels: data.dimension_names,
4780 continuousLine: false,
4781 behaveLikeLine: false
4784 if(state.chart.chart_type === 'line')
4785 state.morris_instance = new Morris.Line(state.morris_options);
4787 else if(state.chart.chart_type === 'area') {
4788 state.morris_options.behaveLikeLine = true;
4789 state.morris_instance = new Morris.Area(state.morris_options);
4792 state.morris_instance = new Morris.Area(state.morris_options);
4797 // ----------------------------------------------------------------------------------------------------------------
4800 NETDATA.raphaelInitialize = function(callback) {
4801 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4803 url: NETDATA.raphael_js,
4806 xhrFields: { withCredentials: true } // required for the cookie
4809 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4812 NETDATA.chartLibraries.raphael.enabled = false;
4813 NETDATA.error(100, NETDATA.raphael_js);
4815 .always(function() {
4816 if(typeof callback === "function")
4821 NETDATA.chartLibraries.raphael.enabled = false;
4822 if(typeof callback === "function")
4827 NETDATA.raphaelChartUpdate = function(state, data) {
4828 $(state.element_chart).raphael(data.result, {
4829 width: state.chartWidth(),
4830 height: state.chartHeight()
4836 NETDATA.raphaelChartCreate = function(state, data) {
4837 $(state.element_chart).raphael(data.result, {
4838 width: state.chartWidth(),
4839 height: state.chartHeight()
4845 // ----------------------------------------------------------------------------------------------------------------
4848 NETDATA.c3Initialize = function(callback) {
4849 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4852 if(!NETDATA.chartLibraries.d3.initialized) {
4853 if(NETDATA.chartLibraries.d3.enabled) {
4854 NETDATA.d3Initialize(function() {
4855 NETDATA.c3Initialize(callback);
4859 NETDATA.chartLibraries.c3.enabled = false;
4860 if(typeof callback === "function")
4865 NETDATA._loadCSS(NETDATA.c3_css);
4871 xhrFields: { withCredentials: true } // required for the cookie
4874 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4877 NETDATA.chartLibraries.c3.enabled = false;
4878 NETDATA.error(100, NETDATA.c3_js);
4880 .always(function() {
4881 if(typeof callback === "function")
4887 NETDATA.chartLibraries.c3.enabled = false;
4888 if(typeof callback === "function")
4893 NETDATA.c3ChartUpdate = function(state, data) {
4894 state.c3_instance.destroy();
4895 return NETDATA.c3ChartCreate(state, data);
4897 //state.c3_instance.load({
4898 // rows: data.result,
4905 NETDATA.c3ChartCreate = function(state, data) {
4907 state.element_chart.id = 'c3-' + state.uuid;
4908 // console.log('id = ' + state.element_chart.id);
4910 state.c3_instance = c3.generate({
4911 bindto: '#' + state.element_chart.id,
4913 width: state.chartWidth(),
4914 height: state.chartHeight()
4917 pattern: state.chartColors()
4922 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4928 format: function(x) {
4929 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4956 // console.log(state.c3_instance);
4961 // ----------------------------------------------------------------------------------------------------------------
4964 NETDATA.d3Initialize = function(callback) {
4965 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4970 xhrFields: { withCredentials: true } // required for the cookie
4973 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4976 NETDATA.chartLibraries.d3.enabled = false;
4977 NETDATA.error(100, NETDATA.d3_js);
4979 .always(function() {
4980 if(typeof callback === "function")
4985 NETDATA.chartLibraries.d3.enabled = false;
4986 if(typeof callback === "function")
4991 NETDATA.d3ChartUpdate = function(state, data) {
4995 NETDATA.d3ChartCreate = function(state, data) {
4999 // ----------------------------------------------------------------------------------------------------------------
5002 NETDATA.googleInitialize = function(callback) {
5003 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5005 url: NETDATA.google_js,
5008 xhrFields: { withCredentials: true } // required for the cookie
5011 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5012 google.load('visualization', '1.1', {
5013 'packages': ['corechart', 'controls'],
5014 'callback': callback
5018 NETDATA.chartLibraries.google.enabled = false;
5019 NETDATA.error(100, NETDATA.google_js);
5020 if(typeof callback === "function")
5025 NETDATA.chartLibraries.google.enabled = false;
5026 if(typeof callback === "function")
5031 NETDATA.googleChartUpdate = function(state, data) {
5032 var datatable = new google.visualization.DataTable(data.result);
5033 state.google_instance.draw(datatable, state.google_options);
5037 NETDATA.googleChartCreate = function(state, data) {
5038 var datatable = new google.visualization.DataTable(data.result);
5040 state.google_options = {
5041 colors: state.chartColors(),
5043 // do not set width, height - the chart resizes itself
5044 //width: state.chartWidth(),
5045 //height: state.chartHeight(),
5050 // title: "Time of Day",
5051 // format:'HH:mm:ss',
5052 viewWindowMode: 'maximized',
5064 viewWindowMode: 'pretty',
5079 focusTarget: 'category',
5086 titlePosition: 'out',
5097 curveType: 'function',
5102 switch(state.chart.chart_type) {
5104 state.google_options.vAxis.viewWindowMode = 'maximized';
5105 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5106 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5110 state.google_options.isStacked = true;
5111 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5112 state.google_options.vAxis.viewWindowMode = 'maximized';
5113 state.google_options.vAxis.minValue = null;
5114 state.google_options.vAxis.maxValue = null;
5115 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5120 state.google_options.lineWidth = 2;
5121 state.google_instance = new google.visualization.LineChart(state.element_chart);
5125 state.google_instance.draw(datatable, state.google_options);
5129 // ----------------------------------------------------------------------------------------------------------------
5131 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5132 if(typeof value !== 'number') value = 0;
5133 if(typeof min !== 'number') min = 0;
5134 if(typeof max !== 'number') max = 0;
5136 if(min > value) min = value;
5137 if(max < value) max = value;
5139 // make sure it is zero based
5140 if(min > 0) min = 0;
5141 if(max < 0) max = 0;
5146 pcent = Math.round(value * 100 / max);
5147 if(pcent === 0) pcent = 0.1;
5151 pcent = Math.round(-value * 100 / min);
5152 if(pcent === 0) pcent = -0.1;
5158 // ----------------------------------------------------------------------------------------------------------------
5161 NETDATA.easypiechartInitialize = function(callback) {
5162 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5164 url: NETDATA.easypiechart_js,
5167 xhrFields: { withCredentials: true } // required for the cookie
5170 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5173 NETDATA.chartLibraries.easypiechart.enabled = false;
5174 NETDATA.error(100, NETDATA.easypiechart_js);
5176 .always(function() {
5177 if(typeof callback === "function")
5182 NETDATA.chartLibraries.easypiechart.enabled = false;
5183 if(typeof callback === "function")
5188 NETDATA.easypiechartClearSelection = function(state) {
5189 if(typeof state.easyPieChartEvent !== 'undefined') {
5190 if(state.easyPieChartEvent.timer !== null)
5191 clearTimeout(state.easyPieChartEvent.timer);
5193 state.easyPieChartEvent.timer = null;
5196 if(state.isAutoRefreshable() === true && state.data !== null) {
5197 NETDATA.easypiechartChartUpdate(state, state.data);
5200 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5201 state.easyPieChart_instance.update(0);
5203 state.easyPieChart_instance.enableAnimation();
5208 NETDATA.easypiechartSetSelection = function(state, t) {
5209 if(state.timeIsVisible(t) !== true)
5210 return NETDATA.easypiechartClearSelection(state);
5212 var slot = state.calculateRowForTime(t);
5213 if(slot < 0 || slot >= state.data.result.length)
5214 return NETDATA.easypiechartClearSelection(state);
5216 if(typeof state.easyPieChartEvent === 'undefined') {
5217 state.easyPieChartEvent = {
5224 var value = state.data.result[state.data.result.length - 1 - slot];
5225 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5226 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5227 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5229 state.easyPieChartEvent.value = value;
5230 state.easyPieChartEvent.pcent = pcent;
5231 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5233 if(state.easyPieChartEvent.timer === null) {
5234 state.easyPieChart_instance.disableAnimation();
5236 state.easyPieChartEvent.timer = setTimeout(function() {
5237 state.easyPieChartEvent.timer = null;
5238 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5239 }, NETDATA.options.current.charts_selection_animation_delay);
5245 NETDATA.easypiechartChartUpdate = function(state, data) {
5246 var value, min, max, pcent;
5248 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5253 value = data.result[0];
5254 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5255 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5256 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5259 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5260 state.easyPieChart_instance.update(pcent);
5264 NETDATA.easypiechartChartCreate = function(state, data) {
5265 var self = $(state.element);
5266 var chart = $(state.element_chart);
5268 var value = data.result[0];
5269 var min = self.data('easypiechart-min-value') || null;
5270 var max = self.data('easypiechart-max-value') || null;
5271 var adjust = self.data('easypiechart-adjust') || null;
5274 min = NETDATA.commonMin.get(state);
5275 state.easyPieChartMin = null;
5278 state.easyPieChartMin = min;
5281 max = NETDATA.commonMax.get(state);
5282 state.easyPieChartMax = null;
5285 state.easyPieChartMax = max;
5287 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5289 chart.data('data-percent', pcent);
5293 case 'width': size = state.chartHeight(); break;
5294 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5295 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5297 default: size = state.chartWidth(); break;
5299 state.element.style.width = size + 'px';
5300 state.element.style.height = size + 'px';
5302 var stroke = Math.floor(size / 22);
5303 if(stroke < 3) stroke = 2;
5305 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5306 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5307 state.easyPieChartLabel = document.createElement('span');
5308 state.easyPieChartLabel.className = 'easyPieChartLabel';
5309 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5310 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5311 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5312 state.element_chart.appendChild(state.easyPieChartLabel);
5314 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5315 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5316 state.easyPieChartTitle = document.createElement('span');
5317 state.easyPieChartTitle.className = 'easyPieChartTitle';
5318 state.easyPieChartTitle.innerText = state.title;
5319 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5320 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5321 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5322 state.element_chart.appendChild(state.easyPieChartTitle);
5324 var unitfontsize = Math.round(titlefontsize * 0.9);
5325 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5326 state.easyPieChartUnits = document.createElement('span');
5327 state.easyPieChartUnits.className = 'easyPieChartUnits';
5328 state.easyPieChartUnits.innerText = state.units;
5329 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5330 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5331 state.element_chart.appendChild(state.easyPieChartUnits);
5333 var barColor = self.data('easypiechart-barcolor');
5334 if(typeof barColor === 'undefined' || barColor === null)
5335 barColor = state.chartColors()[0];
5337 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5338 var tmp = eval(barColor);
5339 if(typeof tmp === 'function')
5343 chart.easyPieChart({
5345 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5346 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5347 scaleLength: self.data('easypiechart-scalelength') || 5,
5348 lineCap: self.data('easypiechart-linecap') || 'round',
5349 lineWidth: self.data('easypiechart-linewidth') || stroke,
5350 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5351 size: self.data('easypiechart-size') || size,
5352 rotate: self.data('easypiechart-rotate') || 0,
5353 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5354 easing: self.data('easypiechart-easing') || undefined
5357 // when we just re-create the chart
5358 // do not animate the first update
5360 if(typeof state.easyPieChart_instance !== 'undefined')
5363 state.easyPieChart_instance = chart.data('easyPieChart');
5364 if(animate === false) state.easyPieChart_instance.disableAnimation();
5365 state.easyPieChart_instance.update(pcent);
5366 if(animate === false) state.easyPieChart_instance.enableAnimation();
5370 // ----------------------------------------------------------------------------------------------------------------
5373 NETDATA.gaugeInitialize = function(callback) {
5374 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5376 url: NETDATA.gauge_js,
5379 xhrFields: { withCredentials: true } // required for the cookie
5382 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5385 NETDATA.chartLibraries.gauge.enabled = false;
5386 NETDATA.error(100, NETDATA.gauge_js);
5388 .always(function() {
5389 if(typeof callback === "function")
5394 NETDATA.chartLibraries.gauge.enabled = false;
5395 if(typeof callback === "function")
5400 NETDATA.gaugeAnimation = function(state, status) {
5403 if(typeof status === 'boolean' && status === false)
5405 else if(typeof status === 'number')
5408 // console.log('gauge speed ' + speed);
5409 state.gauge_instance.animationSpeed = speed;
5410 state.___gaugeOld__.speed = speed;
5413 NETDATA.gaugeSet = function(state, value, min, max) {
5414 if(typeof value !== 'number') value = 0;
5415 if(typeof min !== 'number') min = 0;
5416 if(typeof max !== 'number') max = 0;
5417 if(value > max) max = value;
5418 if(value < min) min = value;
5427 // gauge.js has an issue if the needle
5428 // is smaller than min or larger than max
5429 // when we set the new values
5430 // the needle will go crazy
5432 // to prevent it, we always feed it
5433 // with a percentage, so that the needle
5434 // is always between min and max
5435 var pcent = (value - min) * 100 / (max - min);
5437 // these should never happen
5438 if(pcent < 0) pcent = 0;
5439 if(pcent > 100) pcent = 100;
5441 state.gauge_instance.set(pcent);
5442 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5444 state.___gaugeOld__.value = value;
5445 state.___gaugeOld__.min = min;
5446 state.___gaugeOld__.max = max;
5449 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5450 if(state.___gaugeOld__.valueLabel !== value) {
5451 state.___gaugeOld__.valueLabel = value;
5452 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5454 if(state.___gaugeOld__.minLabel !== min) {
5455 state.___gaugeOld__.minLabel = min;
5456 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5458 if(state.___gaugeOld__.maxLabel !== max) {
5459 state.___gaugeOld__.maxLabel = max;
5460 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5464 NETDATA.gaugeClearSelection = function(state) {
5465 if(typeof state.gaugeEvent !== 'undefined') {
5466 if(state.gaugeEvent.timer !== null)
5467 clearTimeout(state.gaugeEvent.timer);
5469 state.gaugeEvent.timer = null;
5472 if(state.isAutoRefreshable() === true && state.data !== null) {
5473 NETDATA.gaugeChartUpdate(state, state.data);
5476 NETDATA.gaugeAnimation(state, false);
5477 NETDATA.gaugeSet(state, null, null, null);
5478 NETDATA.gaugeSetLabels(state, null, null, null);
5481 NETDATA.gaugeAnimation(state, true);
5485 NETDATA.gaugeSetSelection = function(state, t) {
5486 if(state.timeIsVisible(t) !== true)
5487 return NETDATA.gaugeClearSelection(state);
5489 var slot = state.calculateRowForTime(t);
5490 if(slot < 0 || slot >= state.data.result.length)
5491 return NETDATA.gaugeClearSelection(state);
5493 if(typeof state.gaugeEvent === 'undefined') {
5494 state.gaugeEvent = {
5502 var value = state.data.result[state.data.result.length - 1 - slot];
5503 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5504 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5506 // make sure it is zero based
5507 if(min > 0) min = 0;
5508 if(max < 0) max = 0;
5510 state.gaugeEvent.value = value;
5511 state.gaugeEvent.min = min;
5512 state.gaugeEvent.max = max;
5513 NETDATA.gaugeSetLabels(state, value, min, max);
5515 if(state.gaugeEvent.timer === null) {
5516 NETDATA.gaugeAnimation(state, false);
5518 state.gaugeEvent.timer = setTimeout(function() {
5519 state.gaugeEvent.timer = null;
5520 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5521 }, NETDATA.options.current.charts_selection_animation_delay);
5527 NETDATA.gaugeChartUpdate = function(state, data) {
5528 var value, min, max;
5530 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5534 NETDATA.gaugeSetLabels(state, null, null, null);
5537 value = data.result[0];
5538 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5539 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5540 if(value < min) min = value;
5541 if(value > max) max = value;
5543 // make sure it is zero based
5544 if(min > 0) min = 0;
5545 if(max < 0) max = 0;
5547 NETDATA.gaugeSetLabels(state, value, min, max);
5550 NETDATA.gaugeSet(state, value, min, max);
5554 NETDATA.gaugeChartCreate = function(state, data) {
5555 var self = $(state.element);
5556 // var chart = $(state.element_chart);
5558 var value = data.result[0];
5559 var min = self.data('gauge-min-value') || null;
5560 var max = self.data('gauge-max-value') || null;
5561 var adjust = self.data('gauge-adjust') || null;
5562 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5563 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5564 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5565 var stopColor = self.data('gauge-stop-color') || void 0;
5566 var generateGradient = self.data('gauge-generate-gradient') || false;
5569 min = NETDATA.commonMin.get(state);
5570 state.gaugeMin = null;
5573 state.gaugeMin = min;
5576 max = NETDATA.commonMax.get(state);
5577 state.gaugeMax = null;
5580 state.gaugeMax = max;
5582 // make sure it is zero based
5583 if(min > 0) min = 0;
5584 if(max < 0) max = 0;
5586 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5588 // case 'width': width = height * ratio; break;
5590 // default: height = width / ratio; break;
5592 //state.element.style.width = width.toString() + 'px';
5593 //state.element.style.height = height.toString() + 'px';
5598 lines: 12, // The number of lines to draw
5599 angle: 0.15, // The length of each line
5600 lineWidth: 0.44, // 0.44 The line thickness
5602 length: 0.8, // 0.9 The radius of the inner circle
5603 strokeWidth: 0.035, // The rotation offset
5604 color: pointerColor // Fill color
5606 colorStart: startColor, // Colors
5607 colorStop: stopColor, // just experiment with them
5608 strokeColor: strokeColor, // to see which ones work best for you
5610 generateGradient: (generateGradient === true)?true:false,
5614 if (generateGradient.constructor === Array) {
5616 // data-gauge-generate-gradient="[0, 50, 100]"
5617 // data-gauge-gradient-percent-color-0="#FFFFFF"
5618 // data-gauge-gradient-percent-color-50="#999900"
5619 // data-gauge-gradient-percent-color-100="#000000"
5621 options.percentColors = new Array();
5622 var len = generateGradient.length;
5624 var pcent = generateGradient[len];
5625 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5626 if(color !== false) {
5627 var a = new Array();
5630 options.percentColors.unshift(a);
5633 if(options.percentColors.length === 0)
5634 delete options.percentColors;
5636 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5637 options.percentColors = [
5638 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5639 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5640 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5641 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5642 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5643 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5644 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5645 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5646 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5647 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5648 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5651 state.gauge_canvas = document.createElement('canvas');
5652 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5653 state.gauge_canvas.className = 'gaugeChart';
5654 state.gauge_canvas.width = width;
5655 state.gauge_canvas.height = height;
5656 state.element_chart.appendChild(state.gauge_canvas);
5658 var valuefontsize = Math.floor(height / 6);
5659 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5660 state.gaugeChartLabel = document.createElement('span');
5661 state.gaugeChartLabel.className = 'gaugeChartLabel';
5662 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5663 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5664 state.element_chart.appendChild(state.gaugeChartLabel);
5666 var titlefontsize = Math.round(valuefontsize / 2);
5668 state.gaugeChartTitle = document.createElement('span');
5669 state.gaugeChartTitle.className = 'gaugeChartTitle';
5670 state.gaugeChartTitle.innerText = state.title;
5671 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5672 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5673 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5674 state.element_chart.appendChild(state.gaugeChartTitle);
5676 var unitfontsize = Math.round(titlefontsize * 0.9);
5677 state.gaugeChartUnits = document.createElement('span');
5678 state.gaugeChartUnits.className = 'gaugeChartUnits';
5679 state.gaugeChartUnits.innerText = state.units;
5680 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5681 state.element_chart.appendChild(state.gaugeChartUnits);
5683 state.gaugeChartMin = document.createElement('span');
5684 state.gaugeChartMin.className = 'gaugeChartMin';
5685 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5686 state.element_chart.appendChild(state.gaugeChartMin);
5688 state.gaugeChartMax = document.createElement('span');
5689 state.gaugeChartMax.className = 'gaugeChartMax';
5690 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5691 state.element_chart.appendChild(state.gaugeChartMax);
5693 // when we just re-create the chart
5694 // do not animate the first update
5696 if(typeof state.gauge_instance !== 'undefined')
5699 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5701 state.___gaugeOld__ = {
5710 // we will always feed a percentage
5711 state.gauge_instance.minValue = 0;
5712 state.gauge_instance.maxValue = 100;
5714 NETDATA.gaugeAnimation(state, animate);
5715 NETDATA.gaugeSet(state, value, min, max);
5716 NETDATA.gaugeSetLabels(state, value, min, max);
5717 NETDATA.gaugeAnimation(state, true);
5721 // ----------------------------------------------------------------------------------------------------------------
5722 // Charts Libraries Registration
5724 NETDATA.chartLibraries = {
5726 initialize: NETDATA.dygraphInitialize,
5727 create: NETDATA.dygraphChartCreate,
5728 update: NETDATA.dygraphChartUpdate,
5729 resize: function(state) {
5730 if(typeof state.dygraph_instance.resize === 'function')
5731 state.dygraph_instance.resize();
5733 setSelection: NETDATA.dygraphSetSelection,
5734 clearSelection: NETDATA.dygraphClearSelection,
5735 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5738 format: function(state) { return 'json'; },
5739 options: function(state) { return 'ms|flip'; },
5740 legend: function(state) {
5741 if(this.isSparkline(state) === false)
5742 return 'right-side';
5746 autoresize: function(state) { return true; },
5747 max_updates_to_recreate: function(state) { return 5000; },
5748 track_colors: function(state) { return true; },
5749 pixels_per_point: function(state) {
5750 if(this.isSparkline(state) === false)
5756 isSparkline: function(state) {
5757 if(typeof state.dygraph_sparkline === 'undefined') {
5758 var t = $(state.element).data('dygraph-theme');
5759 if(t === 'sparkline')
5760 state.dygraph_sparkline = true;
5762 state.dygraph_sparkline = false;
5764 return state.dygraph_sparkline;
5768 initialize: NETDATA.sparklineInitialize,
5769 create: NETDATA.sparklineChartCreate,
5770 update: NETDATA.sparklineChartUpdate,
5772 setSelection: undefined, // function(state, t) { return true; },
5773 clearSelection: undefined, // function(state) { return true; },
5774 toolboxPanAndZoom: null,
5777 format: function(state) { return 'array'; },
5778 options: function(state) { return 'flip|abs'; },
5779 legend: function(state) { return null; },
5780 autoresize: function(state) { return false; },
5781 max_updates_to_recreate: function(state) { return 5000; },
5782 track_colors: function(state) { return false; },
5783 pixels_per_point: function(state) { return 3; }
5786 initialize: NETDATA.peityInitialize,
5787 create: NETDATA.peityChartCreate,
5788 update: NETDATA.peityChartUpdate,
5790 setSelection: undefined, // function(state, t) { return true; },
5791 clearSelection: undefined, // function(state) { return true; },
5792 toolboxPanAndZoom: null,
5795 format: function(state) { return 'ssvcomma'; },
5796 options: function(state) { return 'null2zero|flip|abs'; },
5797 legend: function(state) { return null; },
5798 autoresize: function(state) { return false; },
5799 max_updates_to_recreate: function(state) { return 5000; },
5800 track_colors: function(state) { return false; },
5801 pixels_per_point: function(state) { return 3; }
5804 initialize: NETDATA.morrisInitialize,
5805 create: NETDATA.morrisChartCreate,
5806 update: NETDATA.morrisChartUpdate,
5808 setSelection: undefined, // function(state, t) { return true; },
5809 clearSelection: undefined, // function(state) { return true; },
5810 toolboxPanAndZoom: null,
5813 format: function(state) { return 'json'; },
5814 options: function(state) { return 'objectrows|ms'; },
5815 legend: function(state) { return null; },
5816 autoresize: function(state) { return false; },
5817 max_updates_to_recreate: function(state) { return 50; },
5818 track_colors: function(state) { return false; },
5819 pixels_per_point: function(state) { return 15; }
5822 initialize: NETDATA.googleInitialize,
5823 create: NETDATA.googleChartCreate,
5824 update: NETDATA.googleChartUpdate,
5826 setSelection: undefined, //function(state, t) { return true; },
5827 clearSelection: undefined, //function(state) { return true; },
5828 toolboxPanAndZoom: null,
5831 format: function(state) { return 'datatable'; },
5832 options: function(state) { return ''; },
5833 legend: function(state) { return null; },
5834 autoresize: function(state) { return false; },
5835 max_updates_to_recreate: function(state) { return 300; },
5836 track_colors: function(state) { return false; },
5837 pixels_per_point: function(state) { return 4; }
5840 initialize: NETDATA.raphaelInitialize,
5841 create: NETDATA.raphaelChartCreate,
5842 update: NETDATA.raphaelChartUpdate,
5844 setSelection: undefined, // function(state, t) { return true; },
5845 clearSelection: undefined, // function(state) { return true; },
5846 toolboxPanAndZoom: null,
5849 format: function(state) { return 'json'; },
5850 options: function(state) { return ''; },
5851 legend: function(state) { return null; },
5852 autoresize: function(state) { return false; },
5853 max_updates_to_recreate: function(state) { return 5000; },
5854 track_colors: function(state) { return false; },
5855 pixels_per_point: function(state) { return 3; }
5858 initialize: NETDATA.c3Initialize,
5859 create: NETDATA.c3ChartCreate,
5860 update: NETDATA.c3ChartUpdate,
5862 setSelection: undefined, // function(state, t) { return true; },
5863 clearSelection: undefined, // function(state) { return true; },
5864 toolboxPanAndZoom: null,
5867 format: function(state) { return 'csvjsonarray'; },
5868 options: function(state) { return 'milliseconds'; },
5869 legend: function(state) { return null; },
5870 autoresize: function(state) { return false; },
5871 max_updates_to_recreate: function(state) { return 5000; },
5872 track_colors: function(state) { return false; },
5873 pixels_per_point: function(state) { return 15; }
5876 initialize: NETDATA.d3Initialize,
5877 create: NETDATA.d3ChartCreate,
5878 update: NETDATA.d3ChartUpdate,
5880 setSelection: undefined, // function(state, t) { return true; },
5881 clearSelection: undefined, // function(state) { return true; },
5882 toolboxPanAndZoom: null,
5885 format: function(state) { return 'json'; },
5886 options: function(state) { return ''; },
5887 legend: function(state) { return null; },
5888 autoresize: function(state) { return false; },
5889 max_updates_to_recreate: function(state) { return 5000; },
5890 track_colors: function(state) { return false; },
5891 pixels_per_point: function(state) { return 3; }
5894 initialize: NETDATA.easypiechartInitialize,
5895 create: NETDATA.easypiechartChartCreate,
5896 update: NETDATA.easypiechartChartUpdate,
5898 setSelection: NETDATA.easypiechartSetSelection,
5899 clearSelection: NETDATA.easypiechartClearSelection,
5900 toolboxPanAndZoom: null,
5903 format: function(state) { return 'array'; },
5904 options: function(state) { return 'absolute'; },
5905 legend: function(state) { return null; },
5906 autoresize: function(state) { return false; },
5907 max_updates_to_recreate: function(state) { return 5000; },
5908 track_colors: function(state) { return true; },
5909 pixels_per_point: function(state) { return 3; },
5913 initialize: NETDATA.gaugeInitialize,
5914 create: NETDATA.gaugeChartCreate,
5915 update: NETDATA.gaugeChartUpdate,
5917 setSelection: NETDATA.gaugeSetSelection,
5918 clearSelection: NETDATA.gaugeClearSelection,
5919 toolboxPanAndZoom: null,
5922 format: function(state) { return 'array'; },
5923 options: function(state) { return 'absolute'; },
5924 legend: function(state) { return null; },
5925 autoresize: function(state) { return false; },
5926 max_updates_to_recreate: function(state) { return 5000; },
5927 track_colors: function(state) { return true; },
5928 pixels_per_point: function(state) { return 3; },
5933 NETDATA.registerChartLibrary = function(library, url) {
5934 if(NETDATA.options.debug.libraries === true)
5935 console.log("registering chart library: " + library);
5937 NETDATA.chartLibraries[library].url = url;
5938 NETDATA.chartLibraries[library].initialized = true;
5939 NETDATA.chartLibraries[library].enabled = true;
5942 // ----------------------------------------------------------------------------------------------------------------
5943 // Load required JS libraries and CSS
5945 NETDATA.requiredJs = [
5947 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5949 isAlreadyLoaded: function() {
5950 // check if bootstrap is loaded
5951 if(typeof $().emulateTransitionEnd == 'function')
5954 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5962 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5963 isAlreadyLoaded: function() { return false; }
5967 NETDATA.requiredCSS = [
5969 url: NETDATA.themes.current.bootstrap_css,
5970 isAlreadyLoaded: function() {
5971 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5978 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5979 isAlreadyLoaded: function() { return false; }
5982 url: NETDATA.themes.current.dashboard_css,
5983 isAlreadyLoaded: function() { return false; }
5987 NETDATA.loadedRequiredJs = 0;
5988 NETDATA.loadRequiredJs = function(index, callback) {
5989 if(index >= NETDATA.requiredJs.length) {
5990 if(typeof callback === 'function')
5995 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5996 NETDATA.loadedRequiredJs++;
5997 NETDATA.loadRequiredJs(++index, callback);
6001 if(NETDATA.options.debug.main_loop === true)
6002 console.log('loading ' + NETDATA.requiredJs[index].url);
6005 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6009 url: NETDATA.requiredJs[index].url,
6012 xhrFields: { withCredentials: true } // required for the cookie
6015 if(NETDATA.options.debug.main_loop === true)
6016 console.log('loaded ' + NETDATA.requiredJs[index].url);
6019 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6021 .always(function() {
6022 NETDATA.loadedRequiredJs++;
6025 NETDATA.loadRequiredJs(++index, callback);
6029 NETDATA.loadRequiredJs(++index, callback);
6032 NETDATA.loadRequiredCSS = function(index) {
6033 if(index >= NETDATA.requiredCSS.length)
6036 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6037 NETDATA.loadRequiredCSS(++index);
6041 if(NETDATA.options.debug.main_loop === true)
6042 console.log('loading ' + NETDATA.requiredCSS[index].url);
6044 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6045 NETDATA.loadRequiredCSS(++index);
6049 // ----------------------------------------------------------------------------------------------------------------
6050 // Registry of netdata hosts
6053 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6054 chart_div_offset: 100, // give that space above the chart when scrolling to it
6055 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6056 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6058 ms_penalty: 0, // the time penalty of the next alarm
6059 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6060 // if alarms are shown faster than: one per 500ms
6062 notifications: false, // when true, the browser supports notifications (may not be granted though)
6063 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6064 first_notification_id: 0, // the id of the first alarm_log entry for this session
6065 // this is used to prevent CLEAR notifications for past events
6066 // notifications_shown: new Array(),
6068 server: null, // the server to connect to for fetching alarms
6069 current: null, // the list of raised alarms - updated in the background
6070 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6072 notify: function(entry) {
6073 // console.log('alarm ' + entry.unique_id);
6075 if(entry.updated === true) {
6076 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6080 var value = entry.value;
6081 if(NETDATA.alarms.current !== null) {
6082 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6083 if(typeof t !== 'undefined' && entry.status == t.status)
6087 var name = entry.name.replace(/_/g, ' ');
6088 var status = entry.status.toLowerCase();
6089 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6090 var tag = entry.alarm_id;
6091 var icon = 'images/seo-performance-128.png';
6092 var interaction = false;
6096 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6098 switch(entry.status) {
6106 case 'UNINITIALIZED':
6110 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6111 // console.log('alarm ' + entry.unique_id + ' is not current');
6114 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6115 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6118 title = name + ' back to normal';
6119 icon = 'images/check-mark-2-128-green.png'
6120 interaction = false;
6124 if(entry.old_status === 'CRITICAL')
6125 status = 'demoted to ' + entry.status.toLowerCase();
6127 icon = 'images/alert-128-orange.png';
6128 interaction = false;
6132 if(entry.old_status === 'WARNING')
6133 status = 'escalated to ' + entry.status.toLowerCase();
6135 icon = 'images/alert-128-red.png'
6140 console.log('invalid alarm status ' + entry.status);
6145 // cleanup old notifications with the same alarm_id as this one
6146 // FIXME: it does not seem to work on any web browser!
6147 var len = NETDATA.alarms.notifications_shown.length;
6149 var n = NETDATA.alarms.notifications_shown[len];
6150 if(n.data.alarm_id === entry.alarm_id) {
6151 console.log('removing old alarm ' + n.data.unique_id);
6153 // close the notification
6156 // remove it from the array
6157 NETDATA.alarms.notifications_shown.splice(len, 1);
6158 len = NETDATA.alarms.notifications_shown.length;
6165 setTimeout(function() {
6166 // show this notification
6167 // console.log('new notification: ' + title);
6168 var n = new Notification(title, {
6169 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6171 requireInteraction: interaction,
6172 icon: NETDATA.serverDefault + icon,
6176 n.onclick = function(event) {
6177 event.preventDefault();
6178 NETDATA.alarms.onclick(event.target.data);
6182 // NETDATA.alarms.notifications_shown.push(n);
6183 // console.log(entry);
6184 }, NETDATA.alarms.ms_penalty);
6186 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6190 scrollToChart: function(chart_id) {
6191 if(typeof chart_id === 'string') {
6192 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6193 if(typeof offset !== 'undefined') {
6194 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6201 scrollToAlarm: function(alarm) {
6202 if(typeof alarm === 'object') {
6203 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6205 if(ret === true && NETDATA.options.page_is_visible === false)
6207 // 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.');
6212 notifyAll: function() {
6213 // console.log('FETCHING ALARM LOG');
6214 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6215 // console.log('ALARM LOG FETCHED');
6217 if(data === null || typeof data !== 'object') {
6218 console.log('invalid alarms log response');
6222 if(data.length === 0) {
6223 console.log('received empty alarm log');
6227 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6229 data.sort(function(a, b) {
6230 if(a.unique_id > b.unique_id) return -1;
6231 if(a.unique_id < b.unique_id) return 1;
6235 NETDATA.alarms.ms_penalty = 0;
6237 var len = data.length;
6239 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6240 NETDATA.alarms.notify(data[len]);
6243 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6246 NETDATA.alarms.last_notification_id = data[0].unique_id;
6247 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6248 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6252 check_notifications: function() {
6253 // returns true if we should fire 1+ notifications
6255 if(NETDATA.alarms.notifications !== true) {
6256 // console.log('notifications not available');
6260 if(Notification.permission !== 'granted') {
6261 // console.log('notifications not granted');
6265 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6266 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6268 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6269 // console.log('new alarms detected');
6272 //else console.log('no new alarms');
6274 // else console.log('cannot process alarms');
6279 get: function(what, callback) {
6281 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6285 'Cache-Control': 'no-cache, no-store',
6286 'Pragma': 'no-cache'
6288 xhrFields: { withCredentials: true } // required for the cookie
6290 .done(function(data) {
6291 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6292 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6294 if(typeof callback === 'function')
6298 NETDATA.error(415, NETDATA.alarms.server);
6300 if(typeof callback === 'function')
6305 update_forever: function() {
6306 NETDATA.alarms.get('active', function(data) {
6308 NETDATA.alarms.current = data;
6310 if(NETDATA.alarms.check_notifications() === true) {
6311 NETDATA.alarms.notifyAll();
6314 if (typeof NETDATA.alarms.callback === 'function') {
6315 NETDATA.alarms.callback(data);
6318 // Health monitoring is disabled on this netdata
6319 if(data.status === false) return;
6322 setTimeout(NETDATA.alarms.update_forever, 10000);
6326 get_log: function(last_id, callback) {
6327 // console.log('fetching all log after ' + last_id.toString());
6329 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6333 'Cache-Control': 'no-cache, no-store',
6334 'Pragma': 'no-cache'
6336 xhrFields: { withCredentials: true } // required for the cookie
6338 .done(function(data) {
6339 if(typeof callback === 'function')
6343 NETDATA.error(416, NETDATA.alarms.server);
6345 if(typeof callback === 'function')
6351 var host = NETDATA.serverDefault;
6352 while(host.slice(-1) === '/')
6353 host = host.substring(0, host.length - 1);
6354 NETDATA.alarms.server = host;
6356 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6358 if(NETDATA.alarms.onclick === null)
6359 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6361 if(netdataShowAlarms === true) {
6362 NETDATA.alarms.update_forever();
6364 if('Notification' in window) {
6365 // console.log('notifications available');
6366 NETDATA.alarms.notifications = true;
6368 if(Notification.permission === 'default')
6369 Notification.requestPermission();
6375 // ----------------------------------------------------------------------------------------------------------------
6376 // Registry of netdata hosts
6378 NETDATA.registry = {
6379 server: null, // the netdata registry server
6380 person_guid: null, // the unique ID of this browser / user
6381 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6382 hostname: null, // the hostname of the netdata server that served dashboard.js
6383 machines: null, // the user's other URLs
6384 machines_array: null, // the user's other URLs in an array
6387 parsePersonUrls: function(person_urls) {
6388 // console.log(person_urls);
6389 NETDATA.registry.person_urls = person_urls;
6392 NETDATA.registry.machines = {};
6393 NETDATA.registry.machines_array = new Array();
6395 var now = Date.now();
6396 var apu = person_urls;
6399 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6400 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6406 accesses: apu[i][3],
6408 alternate_urls: new Array()
6410 obj.alternate_urls.push(apu[i][1]);
6412 NETDATA.registry.machines[apu[i][0]] = obj;
6413 NETDATA.registry.machines_array.push(obj);
6416 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6418 var pu = NETDATA.registry.machines[apu[i][0]];
6419 if(pu.last_t < apu[i][2]) {
6421 pu.last_t = apu[i][2];
6422 pu.name = apu[i][4];
6424 pu.accesses += apu[i][3];
6425 pu.alternate_urls.push(apu[i][1]);
6430 if(typeof netdataRegistryCallback === 'function')
6431 netdataRegistryCallback(NETDATA.registry.machines_array);
6435 if(netdataRegistry !== true) return;
6437 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6439 NETDATA.registry.server = data.registry;
6440 NETDATA.registry.machine_guid = data.machine_guid;
6441 NETDATA.registry.hostname = data.hostname;
6443 NETDATA.registry.access(2, function (person_urls) {
6444 NETDATA.registry.parsePersonUrls(person_urls);
6451 hello: function(host, callback) {
6452 while(host.slice(-1) === '/')
6453 host = host.substring(0, host.length - 1);
6455 // send HELLO to a netdata server:
6456 // 1. verifies the server is reachable
6457 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6459 url: host + '/api/v1/registry?action=hello',
6463 'Cache-Control': 'no-cache, no-store',
6464 'Pragma': 'no-cache'
6466 xhrFields: { withCredentials: true } // required for the cookie
6468 .done(function(data) {
6469 if(typeof data.status !== 'string' || data.status !== 'ok') {
6470 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6474 if(typeof callback === 'function')
6478 NETDATA.error(407, host);
6480 if(typeof callback === 'function')
6485 access: function(max_redirects, callback) {
6486 // send ACCESS to a netdata registry:
6487 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6488 // 2. it responds with a list of netdata servers we know
6489 // the registry identifies us using a cookie it sets the first time we access it
6490 // the registry may respond with a redirect URL to send us to another registry
6492 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),
6496 'Cache-Control': 'no-cache, no-store',
6497 'Pragma': 'no-cache'
6499 xhrFields: { withCredentials: true } // required for the cookie
6501 .done(function(data) {
6502 var redirect = null;
6503 if(typeof data.registry === 'string')
6504 redirect = data.registry;
6506 if(typeof data.status !== 'string' || data.status !== 'ok') {
6507 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6512 if(redirect !== null && max_redirects > 0) {
6513 NETDATA.registry.server = redirect;
6514 NETDATA.registry.access(max_redirects - 1, callback);
6517 if(typeof callback === 'function')
6522 if(typeof data.person_guid === 'string')
6523 NETDATA.registry.person_guid = data.person_guid;
6525 if(typeof callback === 'function')
6526 callback(data.urls);
6530 NETDATA.error(410, NETDATA.registry.server);
6532 if(typeof callback === 'function')
6537 delete: function(delete_url, callback) {
6538 // send DELETE to a netdata registry:
6540 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),
6544 'Cache-Control': 'no-cache, no-store',
6545 'Pragma': 'no-cache'
6547 xhrFields: { withCredentials: true } // required for the cookie
6549 .done(function(data) {
6550 if(typeof data.status !== 'string' || data.status !== 'ok') {
6551 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6555 if(typeof callback === 'function')
6559 NETDATA.error(412, NETDATA.registry.server);
6561 if(typeof callback === 'function')
6566 search: function(machine_guid, callback) {
6567 // SEARCH for the URLs of a machine:
6569 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,
6573 'Cache-Control': 'no-cache, no-store',
6574 'Pragma': 'no-cache'
6576 xhrFields: { withCredentials: true } // required for the cookie
6578 .done(function(data) {
6579 if(typeof data.status !== 'string' || data.status !== 'ok') {
6580 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6584 if(typeof callback === 'function')
6588 NETDATA.error(418, NETDATA.registry.server);
6590 if(typeof callback === 'function')
6595 switch: function(new_person_guid, callback) {
6598 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,
6602 'Cache-Control': 'no-cache, no-store',
6603 'Pragma': 'no-cache'
6605 xhrFields: { withCredentials: true } // required for the cookie
6607 .done(function(data) {
6608 if(typeof data.status !== 'string' || data.status !== 'ok') {
6609 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6613 if(typeof callback === 'function')
6617 NETDATA.error(414, NETDATA.registry.server);
6619 if(typeof callback === 'function')
6625 // ----------------------------------------------------------------------------------------------------------------
6628 if(typeof netdataPrepCallback === 'function')
6629 netdataPrepCallback();
6631 NETDATA.errorReset();
6632 NETDATA.loadRequiredCSS(0);
6634 NETDATA._loadjQuery(function() {
6635 NETDATA.loadRequiredJs(0, function() {
6636 if(typeof $().emulateTransitionEnd !== 'function') {
6637 // bootstrap is not available
6638 NETDATA.options.current.show_help = false;
6641 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6642 if(NETDATA.options.debug.main_loop === true)
6643 console.log('starting chart refresh thread');
6649 })(window, document);