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 // Our state object, where all per-chart values are stored
1166 chartState = function(element) {
1167 var self = $(element);
1168 this.element = element;
1171 // all private functions should use 'that', instead of 'this'
1174 /* error() - private
1175 * show an error instead of the chart
1177 var error = function(msg) {
1180 if(typeof netdataErrorCallback === 'function') {
1181 ret = netdataErrorCallback('chart', that.id, msg);
1185 that.element.innerHTML = that.id + ': ' + msg;
1186 that.enabled = false;
1187 that.current = that.pan;
1191 // GUID - a unique identifier for the chart
1192 this.uuid = NETDATA.guid();
1194 // string - the name of chart
1195 this.id = self.data('netdata');
1197 // string - the key for localStorage settings
1198 this.settings_id = self.data('id') || null;
1200 // the user given dimensions of the element
1201 this.width = self.data('width') || NETDATA.chartDefaults.width;
1202 this.height = self.data('height') || NETDATA.chartDefaults.height;
1203 this.height_original = this.height;
1205 if(this.settings_id !== null) {
1206 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1207 // this is the callback that will be called
1208 // if and when the user resets all localStorage variables
1209 // to their defaults
1211 resizeChartToHeight(height);
1215 // string - the netdata server URL, without any path
1216 this.host = self.data('host') || NETDATA.chartDefaults.host;
1218 // make sure the host does not end with /
1219 // all netdata API requests use absolute paths
1220 while(this.host.slice(-1) === '/')
1221 this.host = this.host.substring(0, this.host.length - 1);
1223 // string - the grouping method requested by the user
1224 this.method = self.data('method') || NETDATA.chartDefaults.method;
1226 // the time-range requested by the user
1227 this.after = self.data('after') || NETDATA.chartDefaults.after;
1228 this.before = self.data('before') || NETDATA.chartDefaults.before;
1230 // the pixels per point requested by the user
1231 this.pixels_per_point = self.data('pixels-per-point') || 1;
1232 this.points = self.data('points') || null;
1234 // the dimensions requested by the user
1235 this.dimensions = self.data('dimensions') || null;
1237 // the chart library requested by the user
1238 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1240 // how many retries we have made to load chart data from the server
1241 this.retries_on_data_failures = 0;
1243 // object - the chart library used
1244 this.library = null;
1248 this.colors_assigned = {};
1249 this.colors_available = null;
1251 // the element already created by the user
1252 this.element_message = null;
1254 // the element with the chart
1255 this.element_chart = null;
1257 // the element with the legend of the chart (if created by us)
1258 this.element_legend = null;
1259 this.element_legend_childs = {
1264 perfect_scroller: null, // the container to apply perfect scroller to
1268 this.chart_url = null; // string - the url to download chart info
1269 this.chart = null; // object - the chart as downloaded from the server
1271 this.title = self.data('title') || null; // the title of the chart
1272 this.units = self.data('units') || null; // the units of the chart dimensions
1273 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1274 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1276 this.running = false; // boolean - true when the chart is being refreshed now
1277 this.validated = false; // boolean - has the chart been validated?
1278 this.enabled = true; // boolean - is the chart enabled for refresh?
1279 this.paused = false; // boolean - is the chart paused for any reason?
1280 this.selected = false; // boolean - is the chart shown a selection?
1281 this.debug = false; // boolean - console.log() debug info about this chart
1283 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1284 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1285 this.requested_after = null; // milliseconds - the timestamp of the request after param
1286 this.requested_before = null; // milliseconds - the timestamp of the request before param
1287 this.requested_padding = null;
1288 this.view_after = 0;
1289 this.view_before = 0;
1291 this.value_decimal_detail = -1;
1293 var d = self.data('decimal-digits');
1294 if(typeof d === 'number') {
1295 this.value_decimal_detail = 1;
1297 this.value_decimal_detail *= 10;
1304 force_update_at: 0, // the timestamp to force the update at
1305 force_before_ms: null,
1306 force_after_ms: null
1311 force_update_at: 0, // the timestamp to force the update at
1312 force_before_ms: null,
1313 force_after_ms: null
1318 force_update_at: 0, // the timestamp to force the update at
1319 force_before_ms: null,
1320 force_after_ms: null
1323 // this is a pointer to one of the sub-classes below
1325 this.current = this.auto;
1327 // check the requested library is available
1328 // we don't initialize it here - it will be initialized when
1329 // this chart will be first used
1330 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1331 NETDATA.error(402, that.library_name);
1332 error('chart library "' + that.library_name + '" is not found');
1335 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1336 NETDATA.error(403, that.library_name);
1337 error('chart library "' + that.library_name + '" is not enabled');
1341 that.library = NETDATA.chartLibraries[that.library_name];
1343 // milliseconds - the time the last refresh took
1344 this.refresh_dt_ms = 0;
1346 // if we need to report the rendering speed
1347 // find the element that needs to be updated
1348 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1350 if(refresh_dt_element_name !== null)
1351 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1353 this.refresh_dt_element = null;
1355 this.dimensions_visibility = new dimensionsVisibility(this);
1357 this._updating = false;
1359 // ============================================================================================================
1360 // PRIVATE FUNCTIONS
1362 var createDOM = function() {
1363 if(that.enabled === false) return;
1365 if(that.element_message !== null) that.element_message.innerHTML = '';
1366 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1367 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1369 that.element.innerHTML = '';
1371 that.element_message = document.createElement('div');
1372 that.element_message.className = ' netdata-message hidden';
1373 that.element.appendChild(that.element_message);
1375 that.element_chart = document.createElement('div');
1376 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1377 that.element.appendChild(that.element_chart);
1379 if(that.hasLegend() === true) {
1380 that.element.className = "netdata-container-with-legend";
1381 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1383 that.element_legend = document.createElement('div');
1384 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1385 that.element.appendChild(that.element_legend);
1388 that.element.className = "netdata-container";
1389 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1391 that.element_legend = null;
1393 that.element_legend_childs.series = null;
1395 if(typeof(that.width) === 'string')
1396 $(that.element).css('width', that.width);
1397 else if(typeof(that.width) === 'number')
1398 $(that.element).css('width', that.width + 'px');
1400 if(typeof(that.library.aspect_ratio) === 'undefined') {
1401 if(typeof(that.height) === 'string')
1402 $(that.element).css('height', that.height);
1403 else if(typeof(that.height) === 'number')
1404 $(that.element).css('height', that.height + 'px');
1407 var w = that.element.offsetWidth;
1408 if(w === null || w === 0) {
1409 // the div is hidden
1410 // this will resize the chart when next viewed
1411 that.tm.last_resized = 0;
1414 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1417 if(NETDATA.chartDefaults.min_width !== null)
1418 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1420 that.tm.last_dom_created = Date.now();
1426 * initialize state variables
1427 * destroy all (possibly) created state elements
1428 * create the basic DOM for a chart
1430 var init = function() {
1431 if(that.enabled === false) return;
1433 that.paused = false;
1434 that.selected = false;
1436 that.chart_created = false; // boolean - is the library.create() been called?
1437 that.updates_counter = 0; // numeric - the number of refreshes made so far
1438 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1439 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1442 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1443 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1444 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1446 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1447 last_updated: 0, // the timestamp the chart last updated with data
1448 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1450 // Used with NETDATA.globalPanAndZoom.seq
1451 last_visible_check: 0, // the time we last checked if it is visible
1452 last_resized: 0, // the time the chart was resized
1453 last_hidden: 0, // the time the chart was hidden
1454 last_unhidden: 0, // the time the chart was unhidden
1455 last_autorefreshed: 0 // the time the chart was last refreshed
1458 that.data = null; // the last data as downloaded from the netdata server
1459 that.data_url = 'invalid://'; // string - the last url used to update the chart
1460 that.data_points = 0; // number - the number of points returned from netdata
1461 that.data_after = 0; // milliseconds - the first timestamp of the data
1462 that.data_before = 0; // milliseconds - the last timestamp of the data
1463 that.data_update_every = 0; // milliseconds - the frequency to update the data
1465 that.tm.last_initialized = Date.now();
1468 that.setMode('auto');
1471 var maxMessageFontSize = function() {
1472 // normally we want a font size, as tall as the element
1473 var h = that.element_message.clientHeight;
1475 // but give it some air, 20% let's say, or 5 pixels min
1476 var lost = Math.max(h * 0.2, 5);
1479 // center the text, vertically
1480 var paddingTop = (lost - 5) / 2;
1482 // but check the width too
1483 // it should fit 10 characters in it
1484 var w = that.element_message.clientWidth / 10;
1486 paddingTop += (h - w) / 2;
1490 // and don't make it too huge
1491 // 5% of the screen size is good
1492 if(h > screen.height / 20) {
1493 paddingTop += (h - (screen.height / 20)) / 2;
1494 h = screen.height / 20;
1498 that.element_message.style.fontSize = h.toString() + 'px';
1499 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1502 var showMessage = function(msg) {
1503 that.element_message.className = 'netdata-message';
1504 that.element_message.innerHTML = msg;
1505 that.element_message.style.fontSize = 'x-small';
1506 that.element_message.style.paddingTop = '0px';
1507 that.___messageHidden___ = undefined;
1510 var showMessageIcon = function(icon) {
1511 that.element_message.innerHTML = icon;
1512 that.element_message.className = 'netdata-message icon';
1513 maxMessageFontSize();
1514 that.___messageHidden___ = undefined;
1517 var hideMessage = function() {
1518 if(typeof that.___messageHidden___ === 'undefined') {
1519 that.___messageHidden___ = true;
1520 that.element_message.className = 'netdata-message hidden';
1524 var showRendering = function() {
1526 if(that.chart !== null) {
1527 if(that.chart.chart_type === 'line')
1528 icon = '<i class="fa fa-line-chart"></i>';
1530 icon = '<i class="fa fa-area-chart"></i>';
1533 icon = '<i class="fa fa-area-chart"></i>';
1535 showMessageIcon(icon + ' netdata');
1538 var showLoading = function() {
1539 if(that.chart_created === false) {
1540 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1546 var isHidden = function() {
1547 if(typeof that.___chartIsHidden___ !== 'undefined')
1553 // hide the chart, when it is not visible - called from isVisible()
1554 var hideChart = function() {
1555 // hide it, if it is not already hidden
1556 if(isHidden() === true) return;
1558 if(that.chart_created === true) {
1559 if(NETDATA.options.current.destroy_on_hide === true) {
1560 // we should destroy it
1565 that.element_chart.style.display = 'none';
1566 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1567 that.tm.last_hidden = Date.now();
1570 // This works, but I not sure there are no corner cases somewhere
1571 // so it is commented - if the user has memory issues he can
1572 // set Destroy on Hide for all charts
1573 // that.data = null;
1577 that.___chartIsHidden___ = true;
1580 // unhide the chart, when it is visible - called from isVisible()
1581 var unhideChart = function() {
1582 if(isHidden() === false) return;
1584 that.___chartIsHidden___ = undefined;
1585 that.updates_since_last_unhide = 0;
1587 if(that.chart_created === false) {
1588 // we need to re-initialize it, to show our background
1589 // logo in bootstrap tabs, until the chart loads
1593 that.tm.last_unhidden = Date.now();
1594 that.element_chart.style.display = '';
1595 if(that.element_legend !== null) that.element_legend.style.display = '';
1601 var canBeRendered = function() {
1602 if(isHidden() === true || that.isVisible(true) === false)
1608 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1609 var callChartLibraryUpdateSafely = function(data) {
1612 if(canBeRendered() === false)
1615 if(NETDATA.options.debug.chart_errors === true)
1616 status = that.library.update(that, data);
1619 status = that.library.update(that, data);
1626 if(status === false) {
1627 error('chart failed to be updated as ' + that.library_name);
1634 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1635 var callChartLibraryCreateSafely = function(data) {
1638 if(canBeRendered() === false)
1641 if(NETDATA.options.debug.chart_errors === true)
1642 status = that.library.create(that, data);
1645 status = that.library.create(that, data);
1652 if(status === false) {
1653 error('chart failed to be created as ' + that.library_name);
1657 that.chart_created = true;
1658 that.updates_since_last_creation = 0;
1662 // ----------------------------------------------------------------------------------------------------------------
1665 // resizeChart() - private
1666 // to be called just before the chart library to make sure that
1667 // a properly sized dom is available
1668 var resizeChart = function() {
1669 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1670 if(that.chart_created === false) return;
1672 if(that.needsRecreation()) {
1675 else if(typeof that.library.resize === 'function') {
1676 that.library.resize(that);
1678 if(that.element_legend_childs.perfect_scroller !== null)
1679 Ps.update(that.element_legend_childs.perfect_scroller);
1681 maxMessageFontSize();
1684 that.tm.last_resized = Date.now();
1688 // this is the actual chart resize algorithm
1690 // - resize the entire container
1691 // - update the internal states
1692 // - resize the chart as the div changes height
1693 // - update the scrollbar of the legend
1694 var resizeChartToHeight = function(h) {
1696 that.element.style.height = h;
1698 if(that.settings_id !== null)
1699 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1701 var now = Date.now();
1702 NETDATA.options.last_page_scroll = now;
1703 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1706 that.tm.last_resized = 0;
1710 this.resizeHandler = function(e) {
1713 if(typeof this.event_resize === 'undefined'
1714 || this.event_resize.chart_original_w === 'undefined'
1715 || this.event_resize.chart_original_h === 'undefined')
1716 this.event_resize = {
1717 chart_original_w: this.element.clientWidth,
1718 chart_original_h: this.element.clientHeight,
1722 if(e.type === 'touchstart') {
1723 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1724 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1727 this.event_resize.mouse_start_x = e.clientX;
1728 this.event_resize.mouse_start_y = e.clientY;
1731 this.event_resize.chart_start_w = this.element.clientWidth;
1732 this.event_resize.chart_start_h = this.element.clientHeight;
1733 this.event_resize.chart_last_w = this.element.clientWidth;
1734 this.event_resize.chart_last_h = this.element.clientHeight;
1736 var now = Date.now();
1737 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller != null) {
1738 // double click / double tap event
1740 // console.dir(this.element_legend_childs.content);
1741 // console.dir(this.element_legend_childs.perfect_scroller);
1743 // the optimal height of the chart
1744 // showing the entire legend
1745 var optimal = this.event_resize.chart_last_h
1746 + this.element_legend_childs.perfect_scroller.scrollHeight
1747 - this.element_legend_childs.perfect_scroller.clientHeight;
1749 // if we are not optimal, be optimal
1750 if(this.event_resize.chart_last_h != optimal) {
1751 // 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());
1752 resizeChartToHeight(optimal.toString() + 'px');
1755 // else if the current height is not the original/saved height
1756 // reset to the original/saved height
1757 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h) {
1758 // 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());
1759 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1762 // else if the current height is not the internal default height
1763 // reset to the internal default height
1764 else if((this.event_resize.chart_last_h.toString() + 'px') != this.height_original) {
1765 // 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());
1766 resizeChartToHeight(this.height_original.toString());
1769 // else if the current height is not the firstchild's clientheight
1771 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1772 var parent_rect = this.element.getBoundingClientRect();
1773 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1774 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1776 // console.log(parent_rect);
1777 // console.log(content_rect);
1778 // console.log(wanted);
1780 // 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' );
1781 if(this.event_resize.chart_last_h != wanted)
1782 resizeChartToHeight(wanted.toString() + 'px');
1786 this.event_resize.last = now;
1788 // process movement event
1789 document.onmousemove =
1790 document.ontouchmove =
1791 this.element_legend_childs.resize_handler.onmousemove =
1792 this.element_legend_childs.resize_handler.ontouchmove =
1797 case 'mousemove': y = e.clientY; break;
1798 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1802 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1804 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1805 resizeChartToHeight(newH.toString() + 'px');
1806 that.event_resize.chart_last_h = newH;
1811 // process end event
1812 document.onmouseup =
1813 document.ontouchend =
1814 this.element_legend_childs.resize_handler.onmouseup =
1815 this.element_legend_childs.resize_handler.ontouchend =
1817 // remove all the hooks
1818 document.onmouseup =
1819 document.onmousemove =
1820 document.ontouchmove =
1821 document.ontouchend =
1822 that.element_legend_childs.resize_handler.onmousemove =
1823 that.element_legend_childs.resize_handler.ontouchmove =
1824 that.element_legend_childs.resize_handler.onmouseout =
1825 that.element_legend_childs.resize_handler.onmouseup =
1826 that.element_legend_childs.resize_handler.ontouchend =
1829 // allow auto-refreshes
1830 NETDATA.options.auto_refresher_stop_until = 0;
1836 var noDataToShow = function() {
1837 showMessageIcon('<i class="fa fa-warning"></i> empty');
1838 that.legendUpdateDOM();
1839 that.tm.last_autorefreshed = Date.now();
1840 // that.data_update_every = 30 * 1000;
1841 //that.element_chart.style.display = 'none';
1842 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1843 //that.___chartIsHidden___ = true;
1846 // ============================================================================================================
1849 this.error = function(msg) {
1853 this.setMode = function(m) {
1854 if(this.current !== null && this.current.name === m) return;
1857 this.current = this.auto;
1858 else if(m === 'pan')
1859 this.current = this.pan;
1860 else if(m === 'zoom')
1861 this.current = this.zoom;
1863 this.current = this.auto;
1865 this.current.force_update_at = 0;
1866 this.current.force_before_ms = null;
1867 this.current.force_after_ms = null;
1869 this.tm.last_mode_switch = Date.now();
1872 // ----------------------------------------------------------------------------------------------------------------
1873 // global selection sync
1875 // prevent to global selection sync for some time
1876 this.globalSelectionSyncDelay = function(ms) {
1877 if(NETDATA.options.current.sync_selection === false)
1880 if(typeof ms === 'number')
1881 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1883 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1886 // can we globally apply selection sync?
1887 this.globalSelectionSyncAbility = function() {
1888 if(NETDATA.options.current.sync_selection === false)
1891 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1897 this.globalSelectionSyncIsMaster = function() {
1898 if(NETDATA.globalSelectionSync.state === this)
1904 // this chart is the master of the global selection sync
1905 this.globalSelectionSyncBeMaster = function() {
1907 if(this.globalSelectionSyncIsMaster()) {
1908 if(this.debug === true)
1909 this.log('sync: I am the master already.');
1914 if(NETDATA.globalSelectionSync.state) {
1915 if(this.debug === true)
1916 this.log('sync: I am not the sync master. Resetting global sync.');
1918 this.globalSelectionSyncStop();
1921 // become the master
1922 if(this.debug === true)
1923 this.log('sync: becoming sync master.');
1925 this.selected = true;
1926 NETDATA.globalSelectionSync.state = this;
1928 // find the all slaves
1929 var targets = NETDATA.options.targets;
1930 var len = targets.length;
1935 if(this.debug === true)
1936 st.log('sync: not adding me to sync');
1938 else if(st.globalSelectionSyncIsEligible()) {
1939 if(this.debug === true)
1940 st.log('sync: adding to sync as slave');
1942 st.globalSelectionSyncBeSlave();
1946 // this.globalSelectionSyncDelay(100);
1949 // can the chart participate to the global selection sync as a slave?
1950 this.globalSelectionSyncIsEligible = function() {
1951 if(this.enabled === true
1952 && this.library !== null
1953 && typeof this.library.setSelection === 'function'
1954 && this.isVisible() === true
1955 && this.chart_created === true)
1961 // this chart becomes a slave of the global selection sync
1962 this.globalSelectionSyncBeSlave = function() {
1963 if(NETDATA.globalSelectionSync.state !== this)
1964 NETDATA.globalSelectionSync.slaves.push(this);
1967 // sync all the visible charts to the given time
1968 // this is to be called from the chart libraries
1969 this.globalSelectionSync = function(t) {
1970 if(this.globalSelectionSyncAbility() === false) {
1971 if(this.debug === true)
1972 this.log('sync: cannot sync (yet?).');
1977 if(this.globalSelectionSyncIsMaster() === false) {
1978 if(this.debug === true)
1979 this.log('sync: trying to be sync master.');
1981 this.globalSelectionSyncBeMaster();
1983 if(this.globalSelectionSyncAbility() === false) {
1984 if(this.debug === true)
1985 this.log('sync: cannot sync (yet?).');
1991 NETDATA.globalSelectionSync.last_t = t;
1992 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1997 // stop syncing all charts to the given time
1998 this.globalSelectionSyncStop = function() {
1999 if(NETDATA.globalSelectionSync.slaves.length) {
2000 if(this.debug === true)
2001 this.log('sync: cleaning up...');
2003 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2005 if(that.debug === true)
2006 st.log('sync: not adding me to sync stop');
2009 if(that.debug === true)
2010 st.log('sync: removed slave from sync');
2012 st.clearSelection();
2016 NETDATA.globalSelectionSync.last_t = 0;
2017 NETDATA.globalSelectionSync.slaves = [];
2018 NETDATA.globalSelectionSync.state = null;
2021 this.clearSelection();
2024 this.setSelection = function(t) {
2025 if(typeof this.library.setSelection === 'function') {
2026 if(this.library.setSelection(this, t) === true)
2027 this.selected = true;
2029 this.selected = false;
2031 else this.selected = true;
2033 if(this.selected === true && this.debug === true)
2034 this.log('selection set to ' + t.toString());
2036 return this.selected;
2039 this.clearSelection = function() {
2040 if(this.selected === true) {
2041 if(typeof this.library.clearSelection === 'function') {
2042 if(this.library.clearSelection(this) === true)
2043 this.selected = false;
2045 this.selected = true;
2047 else this.selected = false;
2049 if(this.selected === false && this.debug === true)
2050 this.log('selection cleared');
2055 return this.selected;
2058 // find if a timestamp (ms) is shown in the current chart
2059 this.timeIsVisible = function(t) {
2060 if(t >= this.data_after && t <= this.data_before)
2065 this.calculateRowForTime = function(t) {
2066 if(this.timeIsVisible(t) === false) return -1;
2067 return Math.floor((t - this.data_after) / this.data_update_every);
2070 // ----------------------------------------------------------------------------------------------------------------
2073 this.log = function(msg) {
2074 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2077 this.pauseChart = function() {
2078 if(this.paused === false) {
2079 if(this.debug === true)
2080 this.log('pauseChart()');
2086 this.unpauseChart = function() {
2087 if(this.paused === true) {
2088 if(this.debug === true)
2089 this.log('unpauseChart()');
2091 this.paused = false;
2095 this.resetChart = function(dont_clear_master, dont_update) {
2096 if(this.debug === true)
2097 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2099 if(typeof dont_clear_master === 'undefined')
2100 dont_clear_master = false;
2102 if(typeof dont_update === 'undefined')
2103 dont_update = false;
2105 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2106 if(this.debug === true)
2107 this.log('resetChart() diverting to clearMaster().');
2108 // this will call us back with master === true
2109 NETDATA.globalPanAndZoom.clearMaster();
2113 this.clearSelection();
2115 this.tm.pan_and_zoom_seq = 0;
2117 this.setMode('auto');
2118 this.current.force_update_at = 0;
2119 this.current.force_before_ms = null;
2120 this.current.force_after_ms = null;
2121 this.tm.last_autorefreshed = 0;
2122 this.paused = false;
2123 this.selected = false;
2124 this.enabled = true;
2125 // this.debug = false;
2127 // do not update the chart here
2128 // or the chart will flip-flop when it is the master
2129 // of a selection sync and another chart becomes
2132 if(dont_update !== true && this.isVisible() === true) {
2137 this.updateChartPanOrZoom = function(after, before) {
2138 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2141 if(this.debug === true)
2144 if(before < after) {
2145 if(this.debug === true)
2146 this.log(logme + 'flipped parameters, rejecting it.');
2151 if(typeof this.fixed_min_duration === 'undefined')
2152 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2154 var min_duration = this.fixed_min_duration;
2155 var current_duration = Math.round(this.view_before - this.view_after);
2157 // round the numbers
2158 after = Math.round(after);
2159 before = Math.round(before);
2161 // align them to update_every
2162 // stretching them further away
2163 after -= after % this.data_update_every;
2164 before += this.data_update_every - (before % this.data_update_every);
2166 // the final wanted duration
2167 var wanted_duration = before - after;
2169 // to allow panning, accept just a point below our minimum
2170 if((current_duration - this.data_update_every) < min_duration)
2171 min_duration = current_duration - this.data_update_every;
2173 // we do it, but we adjust to minimum size and return false
2174 // when the wanted size is below the current and the minimum
2176 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2177 if(this.debug === true)
2178 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2180 min_duration = this.fixed_min_duration;
2182 var dt = (min_duration - wanted_duration) / 2;
2185 wanted_duration = before - after;
2189 var tolerance = this.data_update_every * 2;
2190 var movement = Math.abs(before - this.view_before);
2192 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2193 if(this.debug === true)
2194 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);
2198 if(this.current.name === 'auto') {
2199 this.log(logme + 'caller called me with mode: ' + this.current.name);
2200 this.setMode('pan');
2203 if(this.debug === true)
2204 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);
2206 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2207 this.current.force_after_ms = after;
2208 this.current.force_before_ms = before;
2209 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2213 this.legendFormatValue = function(value) {
2214 if(value === null || value === 'undefined') return '-';
2215 if(typeof value !== 'number') return value;
2217 if(this.value_decimal_detail !== -1)
2218 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2220 var abs = Math.abs(value);
2221 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2222 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2223 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2224 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2225 return (Math.round(value * 10000) / 10000).toLocaleString();
2228 this.legendSetLabelValue = function(label, value) {
2229 var series = this.element_legend_childs.series[label];
2230 if(typeof series === 'undefined') return;
2231 if(series.value === null && series.user === null) return;
2234 // this slows down firefox and edge significantly
2235 // since it requires to use innerHTML(), instead of innerText()
2237 // if the value has not changed, skip DOM update
2238 //if(series.last === value) return;
2241 if(typeof value === 'number') {
2242 var v = Math.abs(value);
2243 s = r = this.legendFormatValue(value);
2245 if(typeof series.last === 'number') {
2246 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2247 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2248 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2250 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2260 series.last = value;
2264 var s = this.legendFormatValue(value);
2266 // caching: do not update the update to show the same value again
2267 if(s === series.last_shown_value) return;
2268 series.last_shown_value = s;
2270 if(series.value !== null) series.value.innerText = s;
2271 if(series.user !== null) series.user.innerText = s;
2274 this.__legendSetDateString = function(date) {
2275 if(date !== this.__last_shown_legend_date) {
2276 this.element_legend_childs.title_date.innerText = date;
2277 this.__last_shown_legend_date = date;
2281 this.__legendSetTimeString = function(time) {
2282 if(time !== this.__last_shown_legend_time) {
2283 this.element_legend_childs.title_time.innerText = time;
2284 this.__last_shown_legend_time = time;
2288 this.__legendSetUnitsString = function(units) {
2289 if(units !== this.__last_shown_legend_units) {
2290 this.element_legend_childs.title_units.innerText = units;
2291 this.__last_shown_legend_units = units;
2295 this.legendSetDate = function(ms) {
2296 if(typeof ms !== 'number') {
2297 this.legendShowUndefined();
2301 var d = new Date(ms);
2303 if(this.element_legend_childs.title_date)
2304 this.__legendSetDateString(d.toLocaleDateString());
2306 if(this.element_legend_childs.title_time)
2307 this.__legendSetTimeString(d.toLocaleTimeString());
2309 if(this.element_legend_childs.title_units)
2310 this.__legendSetUnitsString(this.units)
2313 this.legendShowUndefined = function() {
2314 if(this.element_legend_childs.title_date)
2315 this.__legendSetDateString(' ');
2317 if(this.element_legend_childs.title_time)
2318 this.__legendSetTimeString(this.chart.name);
2320 if(this.element_legend_childs.title_units)
2321 this.__legendSetUnitsString(' ')
2323 if(this.data && this.element_legend_childs.series !== null) {
2324 var labels = this.data.dimension_names;
2325 var i = labels.length;
2327 var label = labels[i];
2329 if(typeof label === 'undefined') continue;
2330 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2331 this.legendSetLabelValue(label, null);
2336 this.legendShowLatestValues = function() {
2337 if(this.chart === null) return;
2338 if(this.selected) return;
2340 if(this.data === null || this.element_legend_childs.series === null) {
2341 this.legendShowUndefined();
2345 var show_undefined = true;
2346 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2347 show_undefined = false;
2349 if(show_undefined) {
2350 this.legendShowUndefined();
2354 this.legendSetDate(this.view_before);
2356 var labels = this.data.dimension_names;
2357 var i = labels.length;
2359 var label = labels[i];
2361 if(typeof label === 'undefined') continue;
2362 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2365 this.legendSetLabelValue(label, null);
2367 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2371 this.legendReset = function() {
2372 this.legendShowLatestValues();
2375 // this should be called just ONCE per dimension per chart
2376 this._chartDimensionColor = function(label) {
2377 if(this.colors === null) this.chartColors();
2379 if(typeof this.colors_assigned[label] === 'undefined') {
2380 if(this.colors_available.length === 0) {
2381 var len = NETDATA.themes.current.colors.length;
2383 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2386 this.colors_assigned[label] = this.colors_available.shift();
2388 if(this.debug === true)
2389 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2392 if(this.debug === true)
2393 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2396 this.colors.push(this.colors_assigned[label]);
2397 return this.colors_assigned[label];
2400 this.chartColors = function() {
2401 if(this.colors !== null) return this.colors;
2403 this.colors = new Array();
2404 this.colors_available = new Array();
2406 // add the standard colors
2407 var len = NETDATA.themes.current.colors.length;
2409 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2411 // add the user supplied colors
2412 var c = $(this.element).data('colors');
2413 // this.log('read colors: ' + c);
2414 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2415 if(typeof c !== 'string') {
2416 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2426 this.colors_available.unshift(c[len]);
2427 // this.log('adding color: ' + c[len]);
2436 this.legendUpdateDOM = function() {
2439 // check that the legend DOM is up to date for the downloaded dimensions
2440 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2441 // this.log('the legend does not have any series - requesting legend update');
2444 else if(this.data === null) {
2445 // this.log('the chart does not have any data - requesting legend update');
2448 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2452 var labels = this.data.dimension_names.toString();
2453 if(labels !== this.element_legend_childs.series.labels_key) {
2456 if(this.debug === true)
2457 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2461 if(needed === false) {
2462 // make sure colors available
2465 // do we have to update the current values?
2466 // we do this, only when the visible chart is current
2467 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2468 if(this.debug === true)
2469 this.log('chart is in latest position... updating values on legend...');
2471 //var labels = this.data.dimension_names;
2472 //var i = labels.length;
2474 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2478 if(this.colors === null) {
2479 // this is the first time we update the chart
2480 // let's assign colors to all dimensions
2481 if(this.library.track_colors() === true)
2482 for(var dim in this.chart.dimensions)
2483 this._chartDimensionColor(this.chart.dimensions[dim].name);
2485 // we will re-generate the colors for the chart
2486 // based on the selected dimensions
2489 if(this.debug === true)
2490 this.log('updating Legend DOM');
2492 // mark all dimensions as invalid
2493 this.dimensions_visibility.invalidateAll();
2495 var genLabel = function(state, parent, dim, name, count) {
2496 var color = state._chartDimensionColor(name);
2498 var user_element = null;
2499 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2500 if(user_id === null)
2501 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2502 if(user_id !== null) {
2503 user_element = document.getElementById(user_id) || null;
2504 if (user_element === null)
2505 state.log('Cannot find element with id: ' + user_id);
2508 state.element_legend_childs.series[name] = {
2509 name: document.createElement('span'),
2510 value: document.createElement('span'),
2513 last_shown_value: null
2516 var label = state.element_legend_childs.series[name];
2518 // create the dimension visibility tracking for this label
2519 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2521 var rgb = NETDATA.colorHex2Rgb(color);
2522 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2523 + state.chart.chart_type
2524 + '" style="background-color: '
2525 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2526 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2528 var text = document.createTextNode(' ' + name);
2529 label.name.appendChild(text);
2532 parent.appendChild(document.createElement('br'));
2534 parent.appendChild(label.name);
2535 parent.appendChild(label.value);
2538 var content = document.createElement('div');
2540 if(this.hasLegend()) {
2541 this.element_legend_childs = {
2543 resize_handler: document.createElement('div'),
2544 toolbox: document.createElement('div'),
2545 toolbox_left: document.createElement('div'),
2546 toolbox_right: document.createElement('div'),
2547 toolbox_reset: document.createElement('div'),
2548 toolbox_zoomin: document.createElement('div'),
2549 toolbox_zoomout: document.createElement('div'),
2550 toolbox_volume: document.createElement('div'),
2551 title_date: document.createElement('span'),
2552 title_time: document.createElement('span'),
2553 title_units: document.createElement('span'),
2554 perfect_scroller: document.createElement('div'),
2558 this.element_legend.innerHTML = '';
2560 if(this.library.toolboxPanAndZoom !== null) {
2562 function get_pan_and_zoom_step(event) {
2564 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2566 else if (event.shiftKey)
2567 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2569 else if (event.altKey)
2570 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2573 return NETDATA.options.current.pan_and_zoom_factor;
2576 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2577 this.element.appendChild(this.element_legend_childs.toolbox);
2579 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2580 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2581 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2582 this.element_legend_childs.toolbox_left.onclick = function(e) {
2585 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2586 var before = that.view_before - step;
2587 var after = that.view_after - step;
2588 if(after >= that.netdata_first)
2589 that.library.toolboxPanAndZoom(that, after, before);
2591 if(NETDATA.options.current.show_help === true)
2592 $(this.element_legend_childs.toolbox_left).popover({
2597 placement: 'bottom',
2598 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2600 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>'
2604 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2605 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2606 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2607 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2609 NETDATA.resetAllCharts(that);
2611 if(NETDATA.options.current.show_help === true)
2612 $(this.element_legend_childs.toolbox_reset).popover({
2617 placement: 'bottom',
2618 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2619 title: 'Chart Reset',
2620 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>'
2623 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2624 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2625 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2626 this.element_legend_childs.toolbox_right.onclick = function(e) {
2628 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2629 var before = that.view_before + step;
2630 var after = that.view_after + step;
2631 if(before <= that.netdata_last)
2632 that.library.toolboxPanAndZoom(that, after, before);
2634 if(NETDATA.options.current.show_help === true)
2635 $(this.element_legend_childs.toolbox_right).popover({
2640 placement: 'bottom',
2641 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2643 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>'
2647 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2648 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2649 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2650 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2652 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2653 var before = that.view_before - dt;
2654 var after = that.view_after + dt;
2655 that.library.toolboxPanAndZoom(that, after, before);
2657 if(NETDATA.options.current.show_help === true)
2658 $(this.element_legend_childs.toolbox_zoomin).popover({
2663 placement: 'bottom',
2664 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2665 title: 'Chart Zoom In',
2666 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>'
2669 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2670 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2671 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2672 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2674 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);
2675 var before = that.view_before + dt;
2676 var after = that.view_after - dt;
2678 that.library.toolboxPanAndZoom(that, after, before);
2680 if(NETDATA.options.current.show_help === true)
2681 $(this.element_legend_childs.toolbox_zoomout).popover({
2686 placement: 'bottom',
2687 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2688 title: 'Chart Zoom Out',
2689 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>'
2692 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2693 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2694 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2695 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2696 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2697 //e.preventDefault();
2698 //alert('clicked toolbox_volume on ' + that.id);
2702 this.element_legend_childs.toolbox = null;
2703 this.element_legend_childs.toolbox_left = null;
2704 this.element_legend_childs.toolbox_reset = null;
2705 this.element_legend_childs.toolbox_right = null;
2706 this.element_legend_childs.toolbox_zoomin = null;
2707 this.element_legend_childs.toolbox_zoomout = null;
2708 this.element_legend_childs.toolbox_volume = null;
2711 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2712 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2713 this.element.appendChild(this.element_legend_childs.resize_handler);
2714 if(NETDATA.options.current.show_help === true)
2715 $(this.element_legend_childs.resize_handler).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 Resize',
2723 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>'
2727 this.element_legend_childs.resize_handler.onmousedown =
2729 that.resizeHandler(e);
2733 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2734 that.resizeHandler(e);
2737 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2738 this.element_legend.appendChild(this.element_legend_childs.title_date);
2740 this.element_legend.appendChild(document.createElement('br'));
2742 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2743 this.element_legend.appendChild(this.element_legend_childs.title_time);
2745 this.element_legend.appendChild(document.createElement('br'));
2747 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2748 this.element_legend.appendChild(this.element_legend_childs.title_units);
2750 this.element_legend.appendChild(document.createElement('br'));
2752 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2753 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2755 content.className = 'netdata-legend-series-content';
2756 this.element_legend_childs.perfect_scroller.appendChild(content);
2758 if(NETDATA.options.current.show_help === true)
2759 $(content).popover({
2764 placement: 'bottom',
2765 title: 'Chart Legend',
2766 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2767 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>'
2771 this.element_legend_childs = {
2773 resize_handler: null,
2776 toolbox_right: null,
2777 toolbox_reset: null,
2778 toolbox_zoomin: null,
2779 toolbox_zoomout: null,
2780 toolbox_volume: null,
2784 perfect_scroller: null,
2790 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2791 if(this.debug === true)
2792 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2794 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2795 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2799 var tmp = new Array();
2800 for(var dim in this.chart.dimensions) {
2801 tmp.push(this.chart.dimensions[dim].name);
2802 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2804 this.element_legend_childs.series.labels_key = tmp.toString();
2805 if(this.debug === true)
2806 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2809 // create a hidden div to be used for hidding
2810 // the original legend of the chart library
2811 var el = document.createElement('div');
2812 if(this.element_legend !== null)
2813 this.element_legend.appendChild(el);
2814 el.style.display = 'none';
2816 this.element_legend_childs.hidden = document.createElement('div');
2817 el.appendChild(this.element_legend_childs.hidden);
2819 if(this.element_legend_childs.perfect_scroller !== null) {
2820 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2822 wheelPropagation: true,
2823 swipePropagation: true,
2824 minScrollbarLength: null,
2825 maxScrollbarLength: null,
2826 useBothWheelAxes: false,
2827 suppressScrollX: true,
2828 suppressScrollY: false,
2829 scrollXMarginOffset: 0,
2830 scrollYMarginOffset: 0,
2833 Ps.update(this.element_legend_childs.perfect_scroller);
2836 this.legendShowLatestValues();
2839 this.hasLegend = function() {
2840 if(typeof this.___hasLegendCache___ !== 'undefined')
2841 return this.___hasLegendCache___;
2844 if(this.library && this.library.legend(this) === 'right-side') {
2845 var legend = $(this.element).data('legend') || 'yes';
2846 if(legend === 'yes') leg = true;
2849 this.___hasLegendCache___ = leg;
2853 this.legendWidth = function() {
2854 return (this.hasLegend())?140:0;
2857 this.legendHeight = function() {
2858 return $(this.element).height();
2861 this.chartWidth = function() {
2862 return $(this.element).width() - this.legendWidth();
2865 this.chartHeight = function() {
2866 return $(this.element).height();
2869 this.chartPixelsPerPoint = function() {
2870 // force an options provided detail
2871 var px = this.pixels_per_point;
2873 if(this.library && px < this.library.pixels_per_point(this))
2874 px = this.library.pixels_per_point(this);
2876 if(px < NETDATA.options.current.pixels_per_point)
2877 px = NETDATA.options.current.pixels_per_point;
2882 this.needsRecreation = function() {
2884 this.chart_created === true
2886 && this.library.autoresize() === false
2887 && this.tm.last_resized < NETDATA.options.last_resized
2891 this.chartURL = function() {
2892 var after, before, points_multiplier = 1;
2893 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2894 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2896 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2897 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2898 this.view_after = after * 1000;
2899 this.view_before = before * 1000;
2901 this.requested_padding = null;
2902 points_multiplier = 1;
2904 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2905 this.tm.pan_and_zoom_seq = 0;
2907 before = Math.round(this.current.force_before_ms / 1000);
2908 after = Math.round(this.current.force_after_ms / 1000);
2909 this.view_after = after * 1000;
2910 this.view_before = before * 1000;
2912 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2913 this.requested_padding = Math.round((before - after) / 2);
2914 after -= this.requested_padding;
2915 before += this.requested_padding;
2916 this.requested_padding *= 1000;
2917 points_multiplier = 2;
2920 this.current.force_before_ms = null;
2921 this.current.force_after_ms = null;
2924 this.tm.pan_and_zoom_seq = 0;
2926 before = this.before;
2928 this.view_after = after * 1000;
2929 this.view_before = before * 1000;
2931 this.requested_padding = null;
2932 points_multiplier = 1;
2935 this.requested_after = after * 1000;
2936 this.requested_before = before * 1000;
2938 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2940 // build the data URL
2941 this.data_url = this.host + this.chart.data_url;
2942 this.data_url += "&format=" + this.library.format();
2943 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2944 this.data_url += "&group=" + this.method;
2946 if(this.override_options !== null)
2947 this.data_url += "&options=" + this.override_options.toString();
2949 this.data_url += "&options=" + this.library.options(this);
2951 this.data_url += '|jsonwrap';
2953 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2954 this.data_url += '|nonzero';
2956 if(this.append_options !== null)
2957 this.data_url += '|' + this.append_options.toString();
2960 this.data_url += "&after=" + after.toString();
2963 this.data_url += "&before=" + before.toString();
2966 this.data_url += "&dimensions=" + this.dimensions;
2968 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2969 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2972 this.redrawChart = function() {
2973 if(this.data !== null)
2974 this.updateChartWithData(this.data);
2977 this.updateChartWithData = function(data) {
2978 if(this.debug === true)
2979 this.log('updateChartWithData() called.');
2981 // this may force the chart to be re-created
2985 this.updates_counter++;
2986 this.updates_since_last_unhide++;
2987 this.updates_since_last_creation++;
2989 var started = Date.now();
2991 // if the result is JSON, find the latest update-every
2992 this.data_update_every = data.view_update_every * 1000;
2993 this.data_after = data.after * 1000;
2994 this.data_before = data.before * 1000;
2995 this.netdata_first = data.first_entry * 1000;
2996 this.netdata_last = data.last_entry * 1000;
2997 this.data_points = data.points;
3000 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3001 if(this.view_after < this.data_after) {
3002 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3003 this.view_after = this.data_after;
3006 if(this.view_before > this.data_before) {
3007 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3008 this.view_before = this.data_before;
3012 this.view_after = this.data_after;
3013 this.view_before = this.data_before;
3016 if(this.debug === true) {
3017 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3019 if(this.current.force_after_ms)
3020 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3022 this.log('STATUS: forced : unset');
3024 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3025 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3026 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3027 this.log('STATUS: points : ' + (this.data_points).toString());
3030 if(this.data_points === 0) {
3035 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3036 if(this.debug === true)
3037 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3039 this.chart_created = false;
3042 // check and update the legend
3043 this.legendUpdateDOM();
3045 if(this.chart_created === true
3046 && typeof this.library.update === 'function') {
3048 if(this.debug === true)
3049 this.log('updating chart...');
3051 if(callChartLibraryUpdateSafely(data) === false)
3055 if(this.debug === true)
3056 this.log('creating chart...');
3058 if(callChartLibraryCreateSafely(data) === false)
3062 this.legendShowLatestValues();
3063 if(this.selected === true)
3064 NETDATA.globalSelectionSync.stop();
3066 // update the performance counters
3067 var now = Date.now();
3068 this.tm.last_updated = now;
3070 // don't update last_autorefreshed if this chart is
3071 // forced to be updated with global PanAndZoom
3072 if(NETDATA.globalPanAndZoom.isActive())
3073 this.tm.last_autorefreshed = 0;
3075 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3076 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3078 this.tm.last_autorefreshed = now;
3081 this.refresh_dt_ms = now - started;
3082 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3084 if(this.refresh_dt_element !== null)
3085 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3088 this.updateChart = function(callback) {
3089 if(this.debug === true)
3090 this.log('updateChart() called.');
3092 if(this._updating === true) {
3093 if(this.debug === true)
3094 this.log('I am already updating...');
3096 if(typeof callback === 'function') callback();
3100 // due to late initialization of charts and libraries
3101 // we need to check this too
3102 if(this.enabled === false) {
3103 if(this.debug === true)
3104 this.log('I am not enabled');
3106 if(typeof callback === 'function') callback();
3110 if(canBeRendered() === false) {
3111 if(typeof callback === 'function') callback();
3115 if(this.chart === null) {
3116 this.getChart(function() { that.updateChart(callback); });
3120 if(this.library.initialized === false) {
3121 if(this.library.enabled === true) {
3122 this.library.initialize(function() { that.updateChart(callback); });
3126 error('chart library "' + this.library_name + '" is not available.');
3127 if(typeof callback === 'function') callback();
3132 this.clearSelection();
3135 if(this.debug === true)
3136 this.log('updating from ' + this.data_url);
3138 NETDATA.statistics.refreshes_total++;
3139 NETDATA.statistics.refreshes_active++;
3141 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3142 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3144 this._updating = true;
3146 this.xhr = $.ajax( {
3151 'Cache-Control': 'no-cache, no-store',
3152 'Pragma': 'no-cache'
3154 xhrFields: { withCredentials: true } // required for the cookie
3156 .done(function(data) {
3157 that.xhr = undefined;
3158 that.retries_on_data_failures = 0;
3160 if(that.debug === true)
3161 that.log('data received. updating chart.');
3163 that.updateChartWithData(data);
3165 .fail(function(msg) {
3166 that.xhr = undefined;
3168 if(msg.statusText !== 'abort') {
3169 that.retries_on_data_failures++;
3170 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3171 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3172 that.retries_on_data_failures = 0;
3173 error('data download failed for url: ' + that.data_url);
3176 that.tm.last_autorefreshed = Date.now();
3177 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3181 .always(function() {
3182 that.xhr = undefined;
3184 NETDATA.statistics.refreshes_active--;
3185 that._updating = false;
3186 if(typeof callback === 'function') callback();
3192 this.isVisible = function(nocache) {
3193 if(typeof nocache === 'undefined')
3196 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3198 // caching - we do not evaluate the charts visibility
3199 // if the page has not been scrolled since the last check
3200 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3201 return this.___isVisible___;
3203 this.tm.last_visible_check = Date.now();
3205 var wh = window.innerHeight;
3206 var x = this.element.getBoundingClientRect();
3210 if(x.width === 0 || x.height === 0) {
3212 this.___isVisible___ = false;
3213 return this.___isVisible___;
3216 if(x.top < 0 && -x.top > x.height) {
3217 // the chart is entirely above
3218 ret = -x.top - x.height;
3220 else if(x.top > wh) {
3221 // the chart is entirely below
3225 if(ret > tolerance) {
3226 // the chart is too far
3229 this.___isVisible___ = false;
3230 return this.___isVisible___;
3233 // the chart is inside or very close
3236 this.___isVisible___ = true;
3237 return this.___isVisible___;
3241 this.isAutoRefreshable = function() {
3242 return (this.current.autorefresh);
3245 this.canBeAutoRefreshed = function() {
3246 var now = Date.now();
3248 if(this.running === true) {
3249 if(this.debug === true)
3250 this.log('I am already running');
3255 if(this.enabled === false) {
3256 if(this.debug === true)
3257 this.log('I am not enabled');
3262 if(this.library === null || this.library.enabled === false) {
3263 error('charting library "' + this.library_name + '" is not available');
3264 if(this.debug === true)
3265 this.log('My chart library ' + this.library_name + ' is not available');
3270 if(this.isVisible() === false) {
3271 if(NETDATA.options.debug.visibility === true || this.debug === true)
3272 this.log('I am not visible');
3277 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3278 if(this.debug === true)
3279 this.log('timed force update detected - allowing this update');
3281 this.current.force_update_at = 0;
3285 if(this.isAutoRefreshable() === true) {
3286 // allow the first update, even if the page is not visible
3287 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3288 if(NETDATA.options.debug.focus === true || this.debug === true)
3289 this.log('canBeAutoRefreshed(): page does not have focus');
3294 if(this.needsRecreation() === true) {
3295 if(this.debug === true)
3296 this.log('canBeAutoRefreshed(): needs re-creation.');
3301 // options valid only for autoRefresh()
3302 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3303 if(NETDATA.globalPanAndZoom.isActive()) {
3304 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3305 if(this.debug === true)
3306 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3311 if(this.debug === true)
3312 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3318 if(this.selected === true) {
3319 if(this.debug === true)
3320 this.log('canBeAutoRefreshed(): I have a selection in place.');
3325 if(this.paused === true) {
3326 if(this.debug === true)
3327 this.log('canBeAutoRefreshed(): I am paused.');
3332 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3333 if(this.debug === true)
3334 this.log('canBeAutoRefreshed(): It is time to update me.');
3344 this.autoRefresh = function(callback) {
3345 if(this.canBeAutoRefreshed() === true && this.running === false) {
3348 state.running = true;
3349 state.updateChart(function() {
3350 state.running = false;
3352 if(typeof callback !== 'undefined')
3357 if(typeof callback !== 'undefined')
3362 this._defaultsFromDownloadedChart = function(chart) {
3364 this.chart_url = chart.url;
3365 this.data_update_every = chart.update_every * 1000;
3366 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3367 this.tm.last_info_downloaded = Date.now();
3369 if(this.title === null)
3370 this.title = chart.title;
3372 if(this.units === null)
3373 this.units = chart.units;
3376 // fetch the chart description from the netdata server
3377 this.getChart = function(callback) {
3378 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3380 this._defaultsFromDownloadedChart(this.chart);
3381 if(typeof callback === 'function') callback();
3384 this.chart_url = "/api/v1/chart?chart=" + this.id;
3386 if(this.debug === true)
3387 this.log('downloading ' + this.chart_url);
3390 url: this.host + this.chart_url,
3393 xhrFields: { withCredentials: true } // required for the cookie
3395 .done(function(chart) {
3396 chart.url = that.chart_url;
3397 that._defaultsFromDownloadedChart(chart);
3398 NETDATA.chartRegistry.add(that.host, that.id, chart);
3401 NETDATA.error(404, that.chart_url);
3402 error('chart not found on url "' + that.chart_url + '"');
3404 .always(function() {
3405 if(typeof callback === 'function') callback();
3410 // ============================================================================================================
3416 NETDATA.resetAllCharts = function(state) {
3417 // first clear the global selection sync
3418 // to make sure no chart is in selected state
3419 state.globalSelectionSyncStop();
3421 // there are 2 possibilities here
3422 // a. state is the global Pan and Zoom master
3423 // b. state is not the global Pan and Zoom master
3425 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3428 // clear the global Pan and Zoom
3429 // this will also refresh the master
3430 // and unblock any charts currently mirroring the master
3431 NETDATA.globalPanAndZoom.clearMaster();
3433 // if we were not the master, reset our status too
3434 // this is required because most probably the mouse
3435 // is over this chart, blocking it from auto-refreshing
3436 if(master === false && (state.paused === true || state.selected === true))
3440 // get or create a chart state, given a DOM element
3441 NETDATA.chartState = function(element) {
3442 var state = $(element).data('netdata-state-object') || null;
3443 if(state === null) {
3444 state = new chartState(element);
3445 $(element).data('netdata-state-object', state);
3450 // ----------------------------------------------------------------------------------------------------------------
3451 // Library functions
3453 // Load a script without jquery
3454 // This is used to load jquery - after it is loaded, we use jquery
3455 NETDATA._loadjQuery = function(callback) {
3456 if(typeof jQuery === 'undefined') {
3457 if(NETDATA.options.debug.main_loop === true)
3458 console.log('loading ' + NETDATA.jQuery);
3460 var script = document.createElement('script');
3461 script.type = 'text/javascript';
3462 script.async = true;
3463 script.src = NETDATA.jQuery;
3465 // script.onabort = onError;
3466 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3467 if(typeof callback === "function")
3468 script.onload = callback;
3470 var s = document.getElementsByTagName('script')[0];
3471 s.parentNode.insertBefore(script, s);
3473 else if(typeof callback === "function")
3477 NETDATA._loadCSS = function(filename) {
3478 // don't use jQuery here
3479 // styles are loaded before jQuery
3480 // to eliminate showing an unstyled page to the user
3482 var fileref = document.createElement("link");
3483 fileref.setAttribute("rel", "stylesheet");
3484 fileref.setAttribute("type", "text/css");
3485 fileref.setAttribute("href", filename);
3487 if (typeof fileref !== 'undefined')
3488 document.getElementsByTagName("head")[0].appendChild(fileref);
3491 NETDATA.colorHex2Rgb = function(hex) {
3492 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3493 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3494 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3495 return r + r + g + g + b + b;
3498 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3500 r: parseInt(result[1], 16),
3501 g: parseInt(result[2], 16),
3502 b: parseInt(result[3], 16)
3506 NETDATA.colorLuminance = function(hex, lum) {
3507 // validate hex string
3508 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3510 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3514 // convert to decimal and change luminosity
3515 var rgb = "#", c, i;
3516 for (i = 0; i < 3; i++) {
3517 c = parseInt(hex.substr(i*2,2), 16);
3518 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3519 rgb += ("00"+c).substr(c.length);
3525 NETDATA.guid = function() {
3527 return Math.floor((1 + Math.random()) * 0x10000)
3532 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3535 NETDATA.zeropad = function(x) {
3536 if(x > -10 && x < 10) return '0' + x.toString();
3537 else return x.toString();
3540 // user function to signal us the DOM has been
3542 NETDATA.updatedDom = function() {
3543 NETDATA.options.updated_dom = true;
3546 NETDATA.ready = function(callback) {
3547 NETDATA.options.pauseCallback = callback;
3550 NETDATA.pause = function(callback) {
3551 if(NETDATA.options.pause === true)
3554 NETDATA.options.pauseCallback = callback;
3557 NETDATA.unpause = function() {
3558 NETDATA.options.pauseCallback = null;
3559 NETDATA.options.updated_dom = true;
3560 NETDATA.options.pause = false;
3563 // ----------------------------------------------------------------------------------------------------------------
3565 // this is purely sequencial charts refresher
3566 // it is meant to be autonomous
3567 NETDATA.chartRefresherNoParallel = function(index) {
3568 if(NETDATA.options.debug.mail_loop === true)
3569 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3571 if(NETDATA.options.updated_dom === true) {
3572 // the dom has been updated
3573 // get the dom parts again
3574 NETDATA.parseDom(NETDATA.chartRefresher);
3577 if(index >= NETDATA.options.targets.length) {
3578 if(NETDATA.options.debug.main_loop === true)
3579 console.log('waiting to restart main loop...');
3581 NETDATA.options.auto_refresher_fast_weight = 0;
3583 setTimeout(function() {
3584 NETDATA.chartRefresher();
3585 }, NETDATA.options.current.idle_between_loops);
3588 var state = NETDATA.options.targets[index];
3590 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3591 if(NETDATA.options.debug.main_loop === true)
3592 console.log('fast rendering...');
3594 state.autoRefresh(function() {
3595 NETDATA.chartRefresherNoParallel(++index);
3599 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3600 NETDATA.options.auto_refresher_fast_weight = 0;
3602 setTimeout(function() {
3603 state.autoRefresh(function() {
3604 NETDATA.chartRefresherNoParallel(++index);
3606 }, NETDATA.options.current.idle_between_charts);
3611 // this is part of the parallel refresher
3612 // its cause is to refresh sequencially all the charts
3613 // that depend on chart library initialization
3614 // it will call the parallel refresher back
3615 // as soon as it sees a chart that its chart library
3617 NETDATA.chartRefresher_uninitialized = function() {
3618 if(NETDATA.options.updated_dom === true) {
3619 // the dom has been updated
3620 // get the dom parts again
3621 NETDATA.parseDom(NETDATA.chartRefresher);
3625 if(NETDATA.options.sequencial.length === 0)
3626 NETDATA.chartRefresher();
3628 var state = NETDATA.options.sequencial.pop();
3629 if(state.library.initialized === true)
3630 NETDATA.chartRefresher();
3632 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3636 NETDATA.chartRefresherWaitTime = function() {
3637 return NETDATA.options.current.idle_parallel_loops;
3640 // the default refresher
3641 // it will create 2 sets of charts:
3642 // - the ones that can be refreshed in parallel
3643 // - the ones that depend on something else
3644 // the first set will be executed in parallel
3645 // the second will be given to NETDATA.chartRefresher_uninitialized()
3646 NETDATA.chartRefresher = function() {
3647 // console.log('auto-refresher...');
3649 if(NETDATA.options.pause === true) {
3650 // console.log('auto-refresher is paused');
3651 setTimeout(NETDATA.chartRefresher,
3652 NETDATA.chartRefresherWaitTime());
3656 if(typeof NETDATA.options.pauseCallback === 'function') {
3657 // console.log('auto-refresher is calling pauseCallback');
3658 NETDATA.options.pause = true;
3659 NETDATA.options.pauseCallback();
3660 NETDATA.chartRefresher();
3664 if(NETDATA.options.current.parallel_refresher === false) {
3665 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3666 NETDATA.chartRefresherNoParallel(0);
3670 if(NETDATA.options.updated_dom === true) {
3671 // the dom has been updated
3672 // get the dom parts again
3673 // console.log('auto-refresher is calling parseDom()');
3674 NETDATA.parseDom(NETDATA.chartRefresher);
3678 var parallel = new Array();
3679 var targets = NETDATA.options.targets;
3680 var len = targets.length;
3683 state = targets[len];
3684 if(state.isVisible() === false || state.running === true)
3687 if(state.library.initialized === false) {
3688 if(state.library.enabled === true) {
3689 state.library.initialize(NETDATA.chartRefresher);
3693 state.error('chart library "' + state.library_name + '" is not enabled.');
3697 parallel.unshift(state);
3700 if(parallel.length > 0) {
3701 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3702 // this will execute the jobs in parallel
3703 $(parallel).each(function() {
3708 // console.log('auto-refresher nothing to do');
3711 // run the next refresh iteration
3712 setTimeout(NETDATA.chartRefresher,
3713 NETDATA.chartRefresherWaitTime());
3716 NETDATA.parseDom = function(callback) {
3717 NETDATA.options.last_page_scroll = Date.now();
3718 NETDATA.options.updated_dom = false;
3720 var targets = $('div[data-netdata]'); //.filter(':visible');
3722 if(NETDATA.options.debug.main_loop === true)
3723 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3725 NETDATA.options.targets = new Array();
3726 var len = targets.length;
3728 // the initialization will take care of sizing
3729 // and the "loading..." message
3730 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3733 if(typeof callback === 'function') callback();
3736 // this is the main function - where everything starts
3737 NETDATA.start = function() {
3738 // this should be called only once
3740 NETDATA.options.page_is_visible = true;
3742 $(window).blur(function() {
3743 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3744 NETDATA.options.page_is_visible = false;
3745 if(NETDATA.options.debug.focus === true)
3746 console.log('Lost Focus!');
3750 $(window).focus(function() {
3751 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3752 NETDATA.options.page_is_visible = true;
3753 if(NETDATA.options.debug.focus === true)
3754 console.log('Focus restored!');
3758 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3759 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3760 NETDATA.options.page_is_visible = false;
3761 if(NETDATA.options.debug.focus === true)
3762 console.log('Document has no focus!');
3766 // bootstrap tab switching
3767 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3769 // bootstrap modal switching
3770 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3771 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3773 // bootstrap collapse switching
3774 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3775 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3777 NETDATA.parseDom(NETDATA.chartRefresher);
3779 // Alarms initialization
3780 setTimeout(NETDATA.alarms.init, 1000);
3782 // Registry initialization
3783 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3785 if(typeof netdataCallback === 'function')
3789 // ----------------------------------------------------------------------------------------------------------------
3792 NETDATA.peityInitialize = function(callback) {
3793 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3795 url: NETDATA.peity_js,
3798 xhrFields: { withCredentials: true } // required for the cookie
3801 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3804 NETDATA.chartLibraries.peity.enabled = false;
3805 NETDATA.error(100, NETDATA.peity_js);
3807 .always(function() {
3808 if(typeof callback === "function")
3813 NETDATA.chartLibraries.peity.enabled = false;
3814 if(typeof callback === "function")
3819 NETDATA.peityChartUpdate = function(state, data) {
3820 state.peity_instance.innerHTML = data.result;
3822 if(state.peity_options.stroke !== state.chartColors()[0]) {
3823 state.peity_options.stroke = state.chartColors()[0];
3824 if(state.chart.chart_type === 'line')
3825 state.peity_options.fill = NETDATA.themes.current.background;
3827 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3830 $(state.peity_instance).peity('line', state.peity_options);
3834 NETDATA.peityChartCreate = function(state, data) {
3835 state.peity_instance = document.createElement('div');
3836 state.element_chart.appendChild(state.peity_instance);
3838 var self = $(state.element);
3839 state.peity_options = {
3840 stroke: NETDATA.themes.current.foreground,
3841 strokeWidth: self.data('peity-strokewidth') || 1,
3842 width: state.chartWidth(),
3843 height: state.chartHeight(),
3844 fill: NETDATA.themes.current.foreground
3847 NETDATA.peityChartUpdate(state, data);
3851 // ----------------------------------------------------------------------------------------------------------------
3854 NETDATA.sparklineInitialize = function(callback) {
3855 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3857 url: NETDATA.sparkline_js,
3860 xhrFields: { withCredentials: true } // required for the cookie
3863 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3866 NETDATA.chartLibraries.sparkline.enabled = false;
3867 NETDATA.error(100, NETDATA.sparkline_js);
3869 .always(function() {
3870 if(typeof callback === "function")
3875 NETDATA.chartLibraries.sparkline.enabled = false;
3876 if(typeof callback === "function")
3881 NETDATA.sparklineChartUpdate = function(state, data) {
3882 state.sparkline_options.width = state.chartWidth();
3883 state.sparkline_options.height = state.chartHeight();
3885 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3889 NETDATA.sparklineChartCreate = function(state, data) {
3890 var self = $(state.element);
3891 var type = self.data('sparkline-type') || 'line';
3892 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3893 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3894 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3895 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3896 var composite = self.data('sparkline-composite') || undefined;
3897 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3898 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3899 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3900 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3901 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3902 var spotColor = self.data('sparkline-spotcolor') || undefined;
3903 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3904 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3905 var spotRadius = self.data('sparkline-spotradius') || undefined;
3906 var valueSpots = self.data('sparkline-valuespots') || undefined;
3907 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3908 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3909 var lineWidth = self.data('sparkline-linewidth') || undefined;
3910 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3911 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3912 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3913 var xvalues = self.data('sparkline-xvalues') || undefined;
3914 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3915 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3916 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3917 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3918 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3919 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3920 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3921 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3922 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3923 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3924 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3925 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3926 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3927 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3928 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3929 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3930 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3931 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3932 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3933 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3934 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3935 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3937 if(spotColor === 'disable') spotColor='';
3938 if(minSpotColor === 'disable') minSpotColor='';
3939 if(maxSpotColor === 'disable') maxSpotColor='';
3941 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3943 state.sparkline_options = {
3945 lineColor: lineColor,
3946 fillColor: fillColor,
3947 chartRangeMin: chartRangeMin,
3948 chartRangeMax: chartRangeMax,
3949 composite: composite,
3950 enableTagOptions: enableTagOptions,
3951 tagOptionPrefix: tagOptionPrefix,
3952 tagValuesAttribute: tagValuesAttribute,
3953 disableHiddenCheck: disableHiddenCheck,
3954 defaultPixelsPerValue: defaultPixelsPerValue,
3955 spotColor: spotColor,
3956 minSpotColor: minSpotColor,
3957 maxSpotColor: maxSpotColor,
3958 spotRadius: spotRadius,
3959 valueSpots: valueSpots,
3960 highlightSpotColor: highlightSpotColor,
3961 highlightLineColor: highlightLineColor,
3962 lineWidth: lineWidth,
3963 normalRangeMin: normalRangeMin,
3964 normalRangeMax: normalRangeMax,
3965 drawNormalOnTop: drawNormalOnTop,
3967 chartRangeClip: chartRangeClip,
3968 chartRangeMinX: chartRangeMinX,
3969 chartRangeMaxX: chartRangeMaxX,
3970 disableInteraction: disableInteraction,
3971 disableTooltips: disableTooltips,
3972 disableHighlight: disableHighlight,
3973 highlightLighten: highlightLighten,
3974 highlightColor: highlightColor,
3975 tooltipContainer: tooltipContainer,
3976 tooltipClassname: tooltipClassname,
3977 tooltipChartTitle: state.title,
3978 tooltipFormat: tooltipFormat,
3979 tooltipPrefix: tooltipPrefix,
3980 tooltipSuffix: tooltipSuffix,
3981 tooltipSkipNull: tooltipSkipNull,
3982 tooltipValueLookups: tooltipValueLookups,
3983 tooltipFormatFieldlist: tooltipFormatFieldlist,
3984 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3985 numberFormatter: numberFormatter,
3986 numberDigitGroupSep: numberDigitGroupSep,
3987 numberDecimalMark: numberDecimalMark,
3988 numberDigitGroupCount: numberDigitGroupCount,
3989 animatedZooms: animatedZooms,
3990 width: state.chartWidth(),
3991 height: state.chartHeight()
3994 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3998 // ----------------------------------------------------------------------------------------------------------------
4005 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4006 if(after < state.netdata_first)
4007 after = state.netdata_first;
4009 if(before > state.netdata_last)
4010 before = state.netdata_last;
4012 state.setMode('zoom');
4013 state.globalSelectionSyncStop();
4014 state.globalSelectionSyncDelay();
4015 state.dygraph_user_action = true;
4016 state.dygraph_force_zoom = true;
4017 state.updateChartPanOrZoom(after, before);
4018 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4021 NETDATA.dygraphSetSelection = function(state, t) {
4022 if(typeof state.dygraph_instance !== 'undefined') {
4023 var r = state.calculateRowForTime(t);
4025 state.dygraph_instance.setSelection(r);
4027 state.dygraph_instance.clearSelection();
4028 state.legendShowUndefined();
4035 NETDATA.dygraphClearSelection = function(state, t) {
4036 if(typeof state.dygraph_instance !== 'undefined') {
4037 state.dygraph_instance.clearSelection();
4042 NETDATA.dygraphSmoothInitialize = function(callback) {
4044 url: NETDATA.dygraph_smooth_js,
4047 xhrFields: { withCredentials: true } // required for the cookie
4050 NETDATA.dygraph.smooth = true;
4051 smoothPlotter.smoothing = 0.3;
4054 NETDATA.dygraph.smooth = false;
4056 .always(function() {
4057 if(typeof callback === "function")
4062 NETDATA.dygraphInitialize = function(callback) {
4063 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4065 url: NETDATA.dygraph_js,
4068 xhrFields: { withCredentials: true } // required for the cookie
4071 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4074 NETDATA.chartLibraries.dygraph.enabled = false;
4075 NETDATA.error(100, NETDATA.dygraph_js);
4077 .always(function() {
4078 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4079 NETDATA.dygraphSmoothInitialize(callback);
4080 else if(typeof callback === "function")
4085 NETDATA.chartLibraries.dygraph.enabled = false;
4086 if(typeof callback === "function")
4091 NETDATA.dygraphChartUpdate = function(state, data) {
4092 var dygraph = state.dygraph_instance;
4094 if(typeof dygraph === 'undefined')
4095 return NETDATA.dygraphChartCreate(state, data);
4097 // when the chart is not visible, and hidden
4098 // if there is a window resize, dygraph detects
4099 // its element size as 0x0.
4100 // this will make it re-appear properly
4102 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4106 file: data.result.data,
4107 colors: state.chartColors(),
4108 labels: data.result.labels,
4109 labelsDivWidth: state.chartWidth() - 70,
4110 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4113 if(state.dygraph_force_zoom === true) {
4114 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4115 state.log('dygraphChartUpdate() forced zoom update');
4117 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4118 options.isZoomedIgnoreProgrammaticZoom = true;
4119 state.dygraph_force_zoom = false;
4121 else if(state.current.name !== 'auto') {
4122 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4123 state.log('dygraphChartUpdate() loose update');
4126 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4127 state.log('dygraphChartUpdate() strict update');
4129 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4130 options.isZoomedIgnoreProgrammaticZoom = true;
4133 options.valueRange = state.dygraph_options.valueRange;
4135 var oldMax = null, oldMin = null;
4136 if(state.__commonMin !== null) {
4137 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4138 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4140 if(state.__commonMax !== null) {
4141 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4142 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4145 if(state.dygraph_smooth_eligible === true) {
4146 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4147 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4148 NETDATA.dygraphChartCreate(state, data);
4153 dygraph.updateOptions(options);
4156 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4157 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4158 options.valueRange[0] = NETDATA.commonMin.get(state);
4161 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4162 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4163 options.valueRange[1] = NETDATA.commonMax.get(state);
4167 if(redraw === true) {
4168 // state.log('forcing redraw to adapt to common- min/max');
4169 dygraph.updateOptions(options);
4172 state.dygraph_last_rendered = Date.now();
4176 NETDATA.dygraphChartCreate = function(state, data) {
4177 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4178 state.log('dygraphChartCreate()');
4180 var self = $(state.element);
4182 var chart_type = state.chart.chart_type;
4183 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4184 chart_type = self.data('dygraph-type') || chart_type;
4186 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4187 smooth = self.data('dygraph-smooth') || smooth;
4189 if(NETDATA.dygraph.smooth === false)
4192 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4193 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4195 state.dygraph_options = {
4196 colors: self.data('dygraph-colors') || state.chartColors(),
4198 // leave a few pixels empty on the right of the chart
4199 rightGap: self.data('dygraph-rightgap') || 5,
4200 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4201 showRoller: self.data('dygraph-showroller') || false,
4203 title: self.data('dygraph-title') || state.title,
4204 titleHeight: self.data('dygraph-titleheight') || 19,
4206 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4207 labels: data.result.labels,
4208 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4209 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4210 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4211 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4212 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4215 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4216 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4218 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4219 xRangePad: self.data('dygraph-xrangepad') || 0,
4220 yRangePad: self.data('dygraph-yrangepad') || 1,
4222 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4224 ylabel: state.units,
4225 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4227 // the function to plot the chart
4230 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4231 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4232 strokePattern: self.data('dygraph-strokepattern') || undefined,
4234 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4235 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4236 drawPoints: self.data('dygraph-drawpoints') || false,
4238 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4239 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4241 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4242 pointSize: self.data('dygraph-pointsize') || 1,
4244 // enabling this makes the chart with little square lines
4245 stepPlot: self.data('dygraph-stepplot') || false,
4247 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4248 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4249 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4251 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4252 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4253 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4254 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4256 drawAxis: self.data('dygraph-drawaxis') || true,
4257 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4258 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4259 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4261 drawGrid: self.data('dygraph-drawgrid') || true,
4262 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4263 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4264 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4266 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4267 sigFigs: self.data('dygraph-sigfigs') || null,
4268 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4269 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4271 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4272 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4273 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4275 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4276 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4280 ticker: Dygraph.dateTicker,
4281 axisLabelFormatter: function (d, gran) {
4282 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4284 valueFormatter: function (ms) {
4285 //var d = new Date(ms);
4286 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4288 // no need to return anything here
4295 valueFormatter: function (x) {
4296 // we format legends with the state object
4297 // no need to do anything here
4298 // return (Math.round(x*100) / 100).toLocaleString();
4299 // return state.legendFormatValue(x);
4304 legendFormatter: function(data) {
4305 var elements = state.element_legend_childs;
4307 // if the hidden div is not there
4308 // we are not managing the legend
4309 if(elements.hidden === null) return;
4311 if (typeof data.x !== 'undefined') {
4312 state.legendSetDate(data.x);
4313 var i = data.series.length;
4315 var series = data.series[i];
4316 if(series.isVisible === true)
4317 state.legendSetLabelValue(series.label, series.y);
4323 drawCallback: function(dygraph, is_initial) {
4324 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4325 state.dygraph_user_action = false;
4327 var x_range = dygraph.xAxisRange();
4328 var after = Math.round(x_range[0]);
4329 var before = Math.round(x_range[1]);
4331 if(NETDATA.options.debug.dygraph === true)
4332 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4334 if(before <= state.netdata_last && after >= state.netdata_first)
4335 state.updateChartPanOrZoom(after, before);
4338 zoomCallback: function(minDate, maxDate, yRanges) {
4339 if(NETDATA.options.debug.dygraph === true)
4340 state.log('dygraphZoomCallback()');
4342 state.globalSelectionSyncStop();
4343 state.globalSelectionSyncDelay();
4344 state.setMode('zoom');
4346 // refresh it to the greatest possible zoom level
4347 state.dygraph_user_action = true;
4348 state.dygraph_force_zoom = true;
4349 state.updateChartPanOrZoom(minDate, maxDate);
4351 highlightCallback: function(event, x, points, row, seriesName) {
4352 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4353 state.log('dygraphHighlightCallback()');
4357 // there is a bug in dygraph when the chart is zoomed enough
4358 // the time it thinks is selected is wrong
4359 // here we calculate the time t based on the row number selected
4361 var t = state.data_after + row * state.data_update_every;
4362 // 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);
4364 state.globalSelectionSync(x);
4366 // fix legend zIndex using the internal structures of dygraph legend module
4367 // this works, but it is a hack!
4368 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4370 unhighlightCallback: function(event) {
4371 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4372 state.log('dygraphUnhighlightCallback()');
4374 state.unpauseChart();
4375 state.globalSelectionSyncStop();
4377 interactionModel : {
4378 mousedown: function(event, dygraph, context) {
4379 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4380 state.log('interactionModel.mousedown()');
4382 state.dygraph_user_action = true;
4383 state.globalSelectionSyncStop();
4385 if(NETDATA.options.debug.dygraph === true)
4386 state.log('dygraphMouseDown()');
4388 // Right-click should not initiate a zoom.
4389 if(event.button && event.button === 2) return;
4391 context.initializeMouseDown(event, dygraph, context);
4393 if(event.button && event.button === 1) {
4394 if (event.altKey || event.shiftKey) {
4395 state.setMode('pan');
4396 state.globalSelectionSyncDelay();
4397 Dygraph.startPan(event, dygraph, context);
4400 state.setMode('zoom');
4401 state.globalSelectionSyncDelay();
4402 Dygraph.startZoom(event, dygraph, context);
4406 if (event.altKey || event.shiftKey) {
4407 state.setMode('zoom');
4408 state.globalSelectionSyncDelay();
4409 Dygraph.startZoom(event, dygraph, context);
4412 state.setMode('pan');
4413 state.globalSelectionSyncDelay();
4414 Dygraph.startPan(event, dygraph, context);
4418 mousemove: function(event, dygraph, context) {
4419 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4420 state.log('interactionModel.mousemove()');
4422 if(context.isPanning) {
4423 state.dygraph_user_action = true;
4424 state.globalSelectionSyncStop();
4425 state.globalSelectionSyncDelay();
4426 state.setMode('pan');
4427 context.is2DPan = false;
4428 Dygraph.movePan(event, dygraph, context);
4430 else if(context.isZooming) {
4431 state.dygraph_user_action = true;
4432 state.globalSelectionSyncStop();
4433 state.globalSelectionSyncDelay();
4434 state.setMode('zoom');
4435 Dygraph.moveZoom(event, dygraph, context);
4438 mouseup: function(event, dygraph, context) {
4439 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4440 state.log('interactionModel.mouseup()');
4442 if (context.isPanning) {
4443 state.dygraph_user_action = true;
4444 state.globalSelectionSyncDelay();
4445 Dygraph.endPan(event, dygraph, context);
4447 else if (context.isZooming) {
4448 state.dygraph_user_action = true;
4449 state.globalSelectionSyncDelay();
4450 Dygraph.endZoom(event, dygraph, context);
4453 click: function(event, dygraph, context) {
4454 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4455 state.log('interactionModel.click()');
4457 event.preventDefault();
4459 dblclick: function(event, dygraph, context) {
4460 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4461 state.log('interactionModel.dblclick()');
4462 NETDATA.resetAllCharts(state);
4464 wheel: function(event, dygraph, context) {
4465 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4466 state.log('interactionModel.wheel()');
4468 // Take the offset of a mouse event on the dygraph canvas and
4469 // convert it to a pair of percentages from the bottom left.
4470 // (Not top left, bottom is where the lower value is.)
4471 function offsetToPercentage(g, offsetX, offsetY) {
4472 // This is calculating the pixel offset of the leftmost date.
4473 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4474 var yar0 = g.yAxisRange(0);
4476 // This is calculating the pixel of the higest value. (Top pixel)
4477 var yOffset = g.toDomCoords(null, yar0[1])[1];
4479 // x y w and h are relative to the corner of the drawing area,
4480 // so that the upper corner of the drawing area is (0, 0).
4481 var x = offsetX - xOffset;
4482 var y = offsetY - yOffset;
4484 // This is computing the rightmost pixel, effectively defining the
4486 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4488 // This is computing the lowest pixel, effectively defining the height.
4489 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4491 // Percentage from the left.
4492 var xPct = w === 0 ? 0 : (x / w);
4493 // Percentage from the top.
4494 var yPct = h === 0 ? 0 : (y / h);
4496 // The (1-) part below changes it from "% distance down from the top"
4497 // to "% distance up from the bottom".
4498 return [xPct, (1-yPct)];
4501 // Adjusts [x, y] toward each other by zoomInPercentage%
4502 // Split it so the left/bottom axis gets xBias/yBias of that change and
4503 // tight/top gets (1-xBias)/(1-yBias) of that change.
4505 // If a bias is missing it splits it down the middle.
4506 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4507 xBias = xBias || 0.5;
4508 yBias = yBias || 0.5;
4510 function adjustAxis(axis, zoomInPercentage, bias) {
4511 var delta = axis[1] - axis[0];
4512 var increment = delta * zoomInPercentage;
4513 var foo = [increment * bias, increment * (1-bias)];
4515 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4518 var yAxes = g.yAxisRanges();
4520 for (var i = 0; i < yAxes.length; i++) {
4521 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4524 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4527 if(event.altKey || event.shiftKey) {
4528 state.dygraph_user_action = true;
4530 state.globalSelectionSyncStop();
4531 state.globalSelectionSyncDelay();
4533 // http://dygraphs.com/gallery/interaction-api.js
4535 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4537 normal_def = event.wheelDelta / 40;
4540 normal_def = event.deltaY * -1.2;
4542 var normal = (event.detail) ? event.detail * -1 : normal_def;
4543 var percentage = normal / 50;
4545 if (!(event.offsetX && event.offsetY)){
4546 event.offsetX = event.layerX - event.target.offsetLeft;
4547 event.offsetY = event.layerY - event.target.offsetTop;
4550 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4551 var xPct = percentages[0];
4552 var yPct = percentages[1];
4554 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4555 var after = new_x_range[0];
4556 var before = new_x_range[1];
4558 var first = state.netdata_first + state.data_update_every;
4559 var last = state.netdata_last + state.data_update_every;
4562 after -= (before - last);
4569 state.setMode('zoom');
4570 if(state.updateChartPanOrZoom(after, before) === true)
4571 dygraph.updateOptions({ dateWindow: [ after, before ] });
4573 event.preventDefault();
4576 touchstart: function(event, dygraph, context) {
4577 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4578 state.log('interactionModel.touchstart()');
4580 state.dygraph_user_action = true;
4581 state.setMode('zoom');
4584 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4586 // we overwrite the touch directions at the end, to overwrite
4587 // the internal default of dygraphs
4588 context.touchDirections = { x: true, y: false };
4590 state.dygraph_last_touch_start = Date.now();
4591 state.dygraph_last_touch_move = 0;
4593 if(typeof event.touches[0].pageX === 'number')
4594 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4596 state.dygraph_last_touch_page_x = 0;
4598 touchmove: function(event, dygraph, context) {
4599 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4600 state.log('interactionModel.touchmove()');
4602 state.dygraph_user_action = true;
4603 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4605 state.dygraph_last_touch_move = Date.now();
4607 touchend: function(event, dygraph, context) {
4608 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4609 state.log('interactionModel.touchend()');
4611 state.dygraph_user_action = true;
4612 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4614 // if it didn't move, it is a selection
4615 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4616 // internal api of dygraphs
4617 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4618 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4619 if(NETDATA.dygraphSetSelection(state, t) === true)
4620 state.globalSelectionSync(t);
4623 // if it was double tap within double click time, reset the charts
4624 var now = Date.now();
4625 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4626 if(state.dygraph_last_touch_move === 0) {
4627 var dt = now - state.dygraph_last_touch_end;
4628 if(dt <= NETDATA.options.current.double_click_speed)
4629 NETDATA.resetAllCharts(state);
4633 // remember the timestamp of the last touch end
4634 state.dygraph_last_touch_end = now;
4639 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4640 state.dygraph_options.drawGrid = false;
4641 state.dygraph_options.drawAxis = false;
4642 state.dygraph_options.title = undefined;
4643 state.dygraph_options.ylabel = undefined;
4644 state.dygraph_options.yLabelWidth = 0;
4645 state.dygraph_options.labelsDivWidth = 120;
4646 state.dygraph_options.labelsDivStyles.width = '120px';
4647 state.dygraph_options.labelsSeparateLines = true;
4648 state.dygraph_options.rightGap = 0;
4649 state.dygraph_options.yRangePad = 1;
4652 if(smooth === true) {
4653 state.dygraph_smooth_eligible = true;
4655 if(NETDATA.options.current.smooth_plot === true)
4656 state.dygraph_options.plotter = smoothPlotter;
4658 else state.dygraph_smooth_eligible = false;
4660 state.dygraph_instance = new Dygraph(state.element_chart,
4661 data.result.data, state.dygraph_options);
4663 state.dygraph_force_zoom = false;
4664 state.dygraph_user_action = false;
4665 state.dygraph_last_rendered = Date.now();
4667 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4668 state.__commonMin = self.data('common-min') || null;
4669 state.__commonMax = self.data('common-max') || null;
4672 state.log('incompatible version of dygraphs detected');
4673 state.__commonMin = null;
4674 state.__commonMax = null;
4680 // ----------------------------------------------------------------------------------------------------------------
4683 NETDATA.morrisInitialize = function(callback) {
4684 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4686 // morris requires raphael
4687 if(!NETDATA.chartLibraries.raphael.initialized) {
4688 if(NETDATA.chartLibraries.raphael.enabled) {
4689 NETDATA.raphaelInitialize(function() {
4690 NETDATA.morrisInitialize(callback);
4694 NETDATA.chartLibraries.morris.enabled = false;
4695 if(typeof callback === "function")
4700 NETDATA._loadCSS(NETDATA.morris_css);
4703 url: NETDATA.morris_js,
4706 xhrFields: { withCredentials: true } // required for the cookie
4709 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4712 NETDATA.chartLibraries.morris.enabled = false;
4713 NETDATA.error(100, NETDATA.morris_js);
4715 .always(function() {
4716 if(typeof callback === "function")
4722 NETDATA.chartLibraries.morris.enabled = false;
4723 if(typeof callback === "function")
4728 NETDATA.morrisChartUpdate = function(state, data) {
4729 state.morris_instance.setData(data.result.data);
4733 NETDATA.morrisChartCreate = function(state, data) {
4735 state.morris_options = {
4736 element: state.element_chart.id,
4737 data: data.result.data,
4739 ykeys: data.dimension_names,
4740 labels: data.dimension_names,
4746 continuousLine: false,
4747 behaveLikeLine: false
4750 if(state.chart.chart_type === 'line')
4751 state.morris_instance = new Morris.Line(state.morris_options);
4753 else if(state.chart.chart_type === 'area') {
4754 state.morris_options.behaveLikeLine = true;
4755 state.morris_instance = new Morris.Area(state.morris_options);
4758 state.morris_instance = new Morris.Area(state.morris_options);
4763 // ----------------------------------------------------------------------------------------------------------------
4766 NETDATA.raphaelInitialize = function(callback) {
4767 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4769 url: NETDATA.raphael_js,
4772 xhrFields: { withCredentials: true } // required for the cookie
4775 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4778 NETDATA.chartLibraries.raphael.enabled = false;
4779 NETDATA.error(100, NETDATA.raphael_js);
4781 .always(function() {
4782 if(typeof callback === "function")
4787 NETDATA.chartLibraries.raphael.enabled = false;
4788 if(typeof callback === "function")
4793 NETDATA.raphaelChartUpdate = function(state, data) {
4794 $(state.element_chart).raphael(data.result, {
4795 width: state.chartWidth(),
4796 height: state.chartHeight()
4802 NETDATA.raphaelChartCreate = function(state, data) {
4803 $(state.element_chart).raphael(data.result, {
4804 width: state.chartWidth(),
4805 height: state.chartHeight()
4811 // ----------------------------------------------------------------------------------------------------------------
4814 NETDATA.c3Initialize = function(callback) {
4815 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4818 if(!NETDATA.chartLibraries.d3.initialized) {
4819 if(NETDATA.chartLibraries.d3.enabled) {
4820 NETDATA.d3Initialize(function() {
4821 NETDATA.c3Initialize(callback);
4825 NETDATA.chartLibraries.c3.enabled = false;
4826 if(typeof callback === "function")
4831 NETDATA._loadCSS(NETDATA.c3_css);
4837 xhrFields: { withCredentials: true } // required for the cookie
4840 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4843 NETDATA.chartLibraries.c3.enabled = false;
4844 NETDATA.error(100, NETDATA.c3_js);
4846 .always(function() {
4847 if(typeof callback === "function")
4853 NETDATA.chartLibraries.c3.enabled = false;
4854 if(typeof callback === "function")
4859 NETDATA.c3ChartUpdate = function(state, data) {
4860 state.c3_instance.destroy();
4861 return NETDATA.c3ChartCreate(state, data);
4863 //state.c3_instance.load({
4864 // rows: data.result,
4871 NETDATA.c3ChartCreate = function(state, data) {
4873 state.element_chart.id = 'c3-' + state.uuid;
4874 // console.log('id = ' + state.element_chart.id);
4876 state.c3_instance = c3.generate({
4877 bindto: '#' + state.element_chart.id,
4879 width: state.chartWidth(),
4880 height: state.chartHeight()
4883 pattern: state.chartColors()
4888 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4894 format: function(x) {
4895 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4922 // console.log(state.c3_instance);
4927 // ----------------------------------------------------------------------------------------------------------------
4930 NETDATA.d3Initialize = function(callback) {
4931 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4936 xhrFields: { withCredentials: true } // required for the cookie
4939 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4942 NETDATA.chartLibraries.d3.enabled = false;
4943 NETDATA.error(100, NETDATA.d3_js);
4945 .always(function() {
4946 if(typeof callback === "function")
4951 NETDATA.chartLibraries.d3.enabled = false;
4952 if(typeof callback === "function")
4957 NETDATA.d3ChartUpdate = function(state, data) {
4961 NETDATA.d3ChartCreate = function(state, data) {
4965 // ----------------------------------------------------------------------------------------------------------------
4968 NETDATA.googleInitialize = function(callback) {
4969 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4971 url: NETDATA.google_js,
4974 xhrFields: { withCredentials: true } // required for the cookie
4977 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4978 google.load('visualization', '1.1', {
4979 'packages': ['corechart', 'controls'],
4980 'callback': callback
4984 NETDATA.chartLibraries.google.enabled = false;
4985 NETDATA.error(100, NETDATA.google_js);
4986 if(typeof callback === "function")
4991 NETDATA.chartLibraries.google.enabled = false;
4992 if(typeof callback === "function")
4997 NETDATA.googleChartUpdate = function(state, data) {
4998 var datatable = new google.visualization.DataTable(data.result);
4999 state.google_instance.draw(datatable, state.google_options);
5003 NETDATA.googleChartCreate = function(state, data) {
5004 var datatable = new google.visualization.DataTable(data.result);
5006 state.google_options = {
5007 colors: state.chartColors(),
5009 // do not set width, height - the chart resizes itself
5010 //width: state.chartWidth(),
5011 //height: state.chartHeight(),
5016 // title: "Time of Day",
5017 // format:'HH:mm:ss',
5018 viewWindowMode: 'maximized',
5030 viewWindowMode: 'pretty',
5045 focusTarget: 'category',
5052 titlePosition: 'out',
5063 curveType: 'function',
5068 switch(state.chart.chart_type) {
5070 state.google_options.vAxis.viewWindowMode = 'maximized';
5071 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5072 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5076 state.google_options.isStacked = true;
5077 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5078 state.google_options.vAxis.viewWindowMode = 'maximized';
5079 state.google_options.vAxis.minValue = null;
5080 state.google_options.vAxis.maxValue = null;
5081 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5086 state.google_options.lineWidth = 2;
5087 state.google_instance = new google.visualization.LineChart(state.element_chart);
5091 state.google_instance.draw(datatable, state.google_options);
5095 // ----------------------------------------------------------------------------------------------------------------
5097 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5098 if(typeof value !== 'number') value = 0;
5099 if(typeof min !== 'number') min = 0;
5100 if(typeof max !== 'number') max = 0;
5102 if(min > value) min = value;
5103 if(max < value) max = value;
5105 // make sure it is zero based
5106 if(min > 0) min = 0;
5107 if(max < 0) max = 0;
5112 pcent = Math.round(value * 100 / max);
5113 if(pcent === 0) pcent = 0.1;
5117 pcent = Math.round(-value * 100 / min);
5118 if(pcent === 0) pcent = -0.1;
5124 // ----------------------------------------------------------------------------------------------------------------
5127 NETDATA.easypiechartInitialize = function(callback) {
5128 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5130 url: NETDATA.easypiechart_js,
5133 xhrFields: { withCredentials: true } // required for the cookie
5136 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5139 NETDATA.chartLibraries.easypiechart.enabled = false;
5140 NETDATA.error(100, NETDATA.easypiechart_js);
5142 .always(function() {
5143 if(typeof callback === "function")
5148 NETDATA.chartLibraries.easypiechart.enabled = false;
5149 if(typeof callback === "function")
5154 NETDATA.easypiechartClearSelection = function(state) {
5155 if(typeof state.easyPieChartEvent !== 'undefined') {
5156 if(state.easyPieChartEvent.timer !== null)
5157 clearTimeout(state.easyPieChartEvent.timer);
5159 state.easyPieChartEvent.timer = null;
5162 if(state.isAutoRefreshable() === true && state.data !== null) {
5163 NETDATA.easypiechartChartUpdate(state, state.data);
5166 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5167 state.easyPieChart_instance.update(0);
5169 state.easyPieChart_instance.enableAnimation();
5174 NETDATA.easypiechartSetSelection = function(state, t) {
5175 if(state.timeIsVisible(t) !== true)
5176 return NETDATA.easypiechartClearSelection(state);
5178 var slot = state.calculateRowForTime(t);
5179 if(slot < 0 || slot >= state.data.result.length)
5180 return NETDATA.easypiechartClearSelection(state);
5182 if(typeof state.easyPieChartEvent === 'undefined') {
5183 state.easyPieChartEvent = {
5190 var value = state.data.result[state.data.result.length - 1 - slot];
5191 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5192 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5193 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5195 state.easyPieChartEvent.value = value;
5196 state.easyPieChartEvent.pcent = pcent;
5197 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5199 if(state.easyPieChartEvent.timer === null) {
5200 state.easyPieChart_instance.disableAnimation();
5202 state.easyPieChartEvent.timer = setTimeout(function() {
5203 state.easyPieChartEvent.timer = null;
5204 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5205 }, NETDATA.options.current.charts_selection_animation_delay);
5211 NETDATA.easypiechartChartUpdate = function(state, data) {
5212 var value, min, max, pcent;
5214 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5219 value = data.result[0];
5220 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5221 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5222 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5225 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5226 state.easyPieChart_instance.update(pcent);
5230 NETDATA.easypiechartChartCreate = function(state, data) {
5231 var self = $(state.element);
5232 var chart = $(state.element_chart);
5234 var value = data.result[0];
5235 var min = self.data('easypiechart-min-value') || null;
5236 var max = self.data('easypiechart-max-value') || null;
5237 var adjust = self.data('easypiechart-adjust') || null;
5240 min = NETDATA.commonMin.get(state);
5241 state.easyPieChartMin = null;
5244 state.easyPieChartMin = min;
5247 max = NETDATA.commonMax.get(state);
5248 state.easyPieChartMax = null;
5251 state.easyPieChartMax = max;
5253 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5255 chart.data('data-percent', pcent);
5259 case 'width': size = state.chartHeight(); break;
5260 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5261 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5263 default: size = state.chartWidth(); break;
5265 state.element.style.width = size + 'px';
5266 state.element.style.height = size + 'px';
5268 var stroke = Math.floor(size / 22);
5269 if(stroke < 3) stroke = 2;
5271 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5272 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5273 state.easyPieChartLabel = document.createElement('span');
5274 state.easyPieChartLabel.className = 'easyPieChartLabel';
5275 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5276 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5277 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5278 state.element_chart.appendChild(state.easyPieChartLabel);
5280 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5281 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5282 state.easyPieChartTitle = document.createElement('span');
5283 state.easyPieChartTitle.className = 'easyPieChartTitle';
5284 state.easyPieChartTitle.innerText = state.title;
5285 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5286 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5287 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5288 state.element_chart.appendChild(state.easyPieChartTitle);
5290 var unitfontsize = Math.round(titlefontsize * 0.9);
5291 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5292 state.easyPieChartUnits = document.createElement('span');
5293 state.easyPieChartUnits.className = 'easyPieChartUnits';
5294 state.easyPieChartUnits.innerText = state.units;
5295 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5296 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5297 state.element_chart.appendChild(state.easyPieChartUnits);
5299 var barColor = self.data('easypiechart-barcolor');
5300 if(typeof barColor === 'undefined' || barColor === null)
5301 barColor = state.chartColors()[0];
5303 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5304 var tmp = eval(barColor);
5305 if(typeof tmp === 'function')
5309 chart.easyPieChart({
5311 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5312 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5313 scaleLength: self.data('easypiechart-scalelength') || 5,
5314 lineCap: self.data('easypiechart-linecap') || 'round',
5315 lineWidth: self.data('easypiechart-linewidth') || stroke,
5316 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5317 size: self.data('easypiechart-size') || size,
5318 rotate: self.data('easypiechart-rotate') || 0,
5319 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5320 easing: self.data('easypiechart-easing') || undefined
5323 // when we just re-create the chart
5324 // do not animate the first update
5326 if(typeof state.easyPieChart_instance !== 'undefined')
5329 state.easyPieChart_instance = chart.data('easyPieChart');
5330 if(animate === false) state.easyPieChart_instance.disableAnimation();
5331 state.easyPieChart_instance.update(pcent);
5332 if(animate === false) state.easyPieChart_instance.enableAnimation();
5336 // ----------------------------------------------------------------------------------------------------------------
5339 NETDATA.gaugeInitialize = function(callback) {
5340 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5342 url: NETDATA.gauge_js,
5345 xhrFields: { withCredentials: true } // required for the cookie
5348 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5351 NETDATA.chartLibraries.gauge.enabled = false;
5352 NETDATA.error(100, NETDATA.gauge_js);
5354 .always(function() {
5355 if(typeof callback === "function")
5360 NETDATA.chartLibraries.gauge.enabled = false;
5361 if(typeof callback === "function")
5366 NETDATA.gaugeAnimation = function(state, status) {
5369 if(typeof status === 'boolean' && status === false)
5371 else if(typeof status === 'number')
5374 // console.log('gauge speed ' + speed);
5375 state.gauge_instance.animationSpeed = speed;
5376 state.___gaugeOld__.speed = speed;
5379 NETDATA.gaugeSet = function(state, value, min, max) {
5380 if(typeof value !== 'number') value = 0;
5381 if(typeof min !== 'number') min = 0;
5382 if(typeof max !== 'number') max = 0;
5383 if(value > max) max = value;
5384 if(value < min) min = value;
5393 // gauge.js has an issue if the needle
5394 // is smaller than min or larger than max
5395 // when we set the new values
5396 // the needle will go crazy
5398 // to prevent it, we always feed it
5399 // with a percentage, so that the needle
5400 // is always between min and max
5401 var pcent = (value - min) * 100 / (max - min);
5403 // these should never happen
5404 if(pcent < 0) pcent = 0;
5405 if(pcent > 100) pcent = 100;
5407 state.gauge_instance.set(pcent);
5408 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5410 state.___gaugeOld__.value = value;
5411 state.___gaugeOld__.min = min;
5412 state.___gaugeOld__.max = max;
5415 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5416 if(state.___gaugeOld__.valueLabel !== value) {
5417 state.___gaugeOld__.valueLabel = value;
5418 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5420 if(state.___gaugeOld__.minLabel !== min) {
5421 state.___gaugeOld__.minLabel = min;
5422 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5424 if(state.___gaugeOld__.maxLabel !== max) {
5425 state.___gaugeOld__.maxLabel = max;
5426 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5430 NETDATA.gaugeClearSelection = function(state) {
5431 if(typeof state.gaugeEvent !== 'undefined') {
5432 if(state.gaugeEvent.timer !== null)
5433 clearTimeout(state.gaugeEvent.timer);
5435 state.gaugeEvent.timer = null;
5438 if(state.isAutoRefreshable() === true && state.data !== null) {
5439 NETDATA.gaugeChartUpdate(state, state.data);
5442 NETDATA.gaugeAnimation(state, false);
5443 NETDATA.gaugeSet(state, null, null, null);
5444 NETDATA.gaugeSetLabels(state, null, null, null);
5447 NETDATA.gaugeAnimation(state, true);
5451 NETDATA.gaugeSetSelection = function(state, t) {
5452 if(state.timeIsVisible(t) !== true)
5453 return NETDATA.gaugeClearSelection(state);
5455 var slot = state.calculateRowForTime(t);
5456 if(slot < 0 || slot >= state.data.result.length)
5457 return NETDATA.gaugeClearSelection(state);
5459 if(typeof state.gaugeEvent === 'undefined') {
5460 state.gaugeEvent = {
5468 var value = state.data.result[state.data.result.length - 1 - slot];
5469 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5470 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5472 // make sure it is zero based
5473 if(min > 0) min = 0;
5474 if(max < 0) max = 0;
5476 state.gaugeEvent.value = value;
5477 state.gaugeEvent.min = min;
5478 state.gaugeEvent.max = max;
5479 NETDATA.gaugeSetLabels(state, value, min, max);
5481 if(state.gaugeEvent.timer === null) {
5482 NETDATA.gaugeAnimation(state, false);
5484 state.gaugeEvent.timer = setTimeout(function() {
5485 state.gaugeEvent.timer = null;
5486 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5487 }, NETDATA.options.current.charts_selection_animation_delay);
5493 NETDATA.gaugeChartUpdate = function(state, data) {
5494 var value, min, max;
5496 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5500 NETDATA.gaugeSetLabels(state, null, null, null);
5503 value = data.result[0];
5504 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5505 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5506 if(value < min) min = value;
5507 if(value > max) max = value;
5509 // make sure it is zero based
5510 if(min > 0) min = 0;
5511 if(max < 0) max = 0;
5513 NETDATA.gaugeSetLabels(state, value, min, max);
5516 NETDATA.gaugeSet(state, value, min, max);
5520 NETDATA.gaugeChartCreate = function(state, data) {
5521 var self = $(state.element);
5522 // var chart = $(state.element_chart);
5524 var value = data.result[0];
5525 var min = self.data('gauge-min-value') || null;
5526 var max = self.data('gauge-max-value') || null;
5527 var adjust = self.data('gauge-adjust') || null;
5528 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5529 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5530 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5531 var stopColor = self.data('gauge-stop-color') || void 0;
5532 var generateGradient = self.data('gauge-generate-gradient') || false;
5535 min = NETDATA.commonMin.get(state);
5536 state.gaugeMin = null;
5539 state.gaugeMin = min;
5542 max = NETDATA.commonMax.get(state);
5543 state.gaugeMax = null;
5546 state.gaugeMax = max;
5548 // make sure it is zero based
5549 if(min > 0) min = 0;
5550 if(max < 0) max = 0;
5552 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5554 // case 'width': width = height * ratio; break;
5556 // default: height = width / ratio; break;
5558 //state.element.style.width = width.toString() + 'px';
5559 //state.element.style.height = height.toString() + 'px';
5564 lines: 12, // The number of lines to draw
5565 angle: 0.15, // The length of each line
5566 lineWidth: 0.44, // 0.44 The line thickness
5568 length: 0.8, // 0.9 The radius of the inner circle
5569 strokeWidth: 0.035, // The rotation offset
5570 color: pointerColor // Fill color
5572 colorStart: startColor, // Colors
5573 colorStop: stopColor, // just experiment with them
5574 strokeColor: strokeColor, // to see which ones work best for you
5576 generateGradient: (generateGradient === true)?true:false,
5580 if (generateGradient.constructor === Array) {
5582 // data-gauge-generate-gradient="[0, 50, 100]"
5583 // data-gauge-gradient-percent-color-0="#FFFFFF"
5584 // data-gauge-gradient-percent-color-50="#999900"
5585 // data-gauge-gradient-percent-color-100="#000000"
5587 options.percentColors = new Array();
5588 var len = generateGradient.length;
5590 var pcent = generateGradient[len];
5591 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5592 if(color !== false) {
5593 var a = new Array();
5596 options.percentColors.unshift(a);
5599 if(options.percentColors.length === 0)
5600 delete options.percentColors;
5602 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5603 options.percentColors = [
5604 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5605 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5606 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5607 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5608 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5609 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5610 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5611 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5612 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5613 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5614 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5617 state.gauge_canvas = document.createElement('canvas');
5618 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5619 state.gauge_canvas.className = 'gaugeChart';
5620 state.gauge_canvas.width = width;
5621 state.gauge_canvas.height = height;
5622 state.element_chart.appendChild(state.gauge_canvas);
5624 var valuefontsize = Math.floor(height / 6);
5625 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5626 state.gaugeChartLabel = document.createElement('span');
5627 state.gaugeChartLabel.className = 'gaugeChartLabel';
5628 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5629 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5630 state.element_chart.appendChild(state.gaugeChartLabel);
5632 var titlefontsize = Math.round(valuefontsize / 2);
5634 state.gaugeChartTitle = document.createElement('span');
5635 state.gaugeChartTitle.className = 'gaugeChartTitle';
5636 state.gaugeChartTitle.innerText = state.title;
5637 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5638 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5639 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5640 state.element_chart.appendChild(state.gaugeChartTitle);
5642 var unitfontsize = Math.round(titlefontsize * 0.9);
5643 state.gaugeChartUnits = document.createElement('span');
5644 state.gaugeChartUnits.className = 'gaugeChartUnits';
5645 state.gaugeChartUnits.innerText = state.units;
5646 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5647 state.element_chart.appendChild(state.gaugeChartUnits);
5649 state.gaugeChartMin = document.createElement('span');
5650 state.gaugeChartMin.className = 'gaugeChartMin';
5651 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5652 state.element_chart.appendChild(state.gaugeChartMin);
5654 state.gaugeChartMax = document.createElement('span');
5655 state.gaugeChartMax.className = 'gaugeChartMax';
5656 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5657 state.element_chart.appendChild(state.gaugeChartMax);
5659 // when we just re-create the chart
5660 // do not animate the first update
5662 if(typeof state.gauge_instance !== 'undefined')
5665 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5667 state.___gaugeOld__ = {
5676 // we will always feed a percentage
5677 state.gauge_instance.minValue = 0;
5678 state.gauge_instance.maxValue = 100;
5680 NETDATA.gaugeAnimation(state, animate);
5681 NETDATA.gaugeSet(state, value, min, max);
5682 NETDATA.gaugeSetLabels(state, value, min, max);
5683 NETDATA.gaugeAnimation(state, true);
5687 // ----------------------------------------------------------------------------------------------------------------
5688 // Charts Libraries Registration
5690 NETDATA.chartLibraries = {
5692 initialize: NETDATA.dygraphInitialize,
5693 create: NETDATA.dygraphChartCreate,
5694 update: NETDATA.dygraphChartUpdate,
5695 resize: function(state) {
5696 if(typeof state.dygraph_instance.resize === 'function')
5697 state.dygraph_instance.resize();
5699 setSelection: NETDATA.dygraphSetSelection,
5700 clearSelection: NETDATA.dygraphClearSelection,
5701 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5704 format: function(state) { return 'json'; },
5705 options: function(state) { return 'ms|flip'; },
5706 legend: function(state) {
5707 if(this.isSparkline(state) === false)
5708 return 'right-side';
5712 autoresize: function(state) { return true; },
5713 max_updates_to_recreate: function(state) { return 5000; },
5714 track_colors: function(state) { return true; },
5715 pixels_per_point: function(state) {
5716 if(this.isSparkline(state) === false)
5722 isSparkline: function(state) {
5723 if(typeof state.dygraph_sparkline === 'undefined') {
5724 var t = $(state.element).data('dygraph-theme');
5725 if(t === 'sparkline')
5726 state.dygraph_sparkline = true;
5728 state.dygraph_sparkline = false;
5730 return state.dygraph_sparkline;
5734 initialize: NETDATA.sparklineInitialize,
5735 create: NETDATA.sparklineChartCreate,
5736 update: NETDATA.sparklineChartUpdate,
5738 setSelection: undefined, // function(state, t) { return true; },
5739 clearSelection: undefined, // function(state) { return true; },
5740 toolboxPanAndZoom: null,
5743 format: function(state) { return 'array'; },
5744 options: function(state) { return 'flip|abs'; },
5745 legend: function(state) { return null; },
5746 autoresize: function(state) { return false; },
5747 max_updates_to_recreate: function(state) { return 5000; },
5748 track_colors: function(state) { return false; },
5749 pixels_per_point: function(state) { return 3; }
5752 initialize: NETDATA.peityInitialize,
5753 create: NETDATA.peityChartCreate,
5754 update: NETDATA.peityChartUpdate,
5756 setSelection: undefined, // function(state, t) { return true; },
5757 clearSelection: undefined, // function(state) { return true; },
5758 toolboxPanAndZoom: null,
5761 format: function(state) { return 'ssvcomma'; },
5762 options: function(state) { return 'null2zero|flip|abs'; },
5763 legend: function(state) { return null; },
5764 autoresize: function(state) { return false; },
5765 max_updates_to_recreate: function(state) { return 5000; },
5766 track_colors: function(state) { return false; },
5767 pixels_per_point: function(state) { return 3; }
5770 initialize: NETDATA.morrisInitialize,
5771 create: NETDATA.morrisChartCreate,
5772 update: NETDATA.morrisChartUpdate,
5774 setSelection: undefined, // function(state, t) { return true; },
5775 clearSelection: undefined, // function(state) { return true; },
5776 toolboxPanAndZoom: null,
5779 format: function(state) { return 'json'; },
5780 options: function(state) { return 'objectrows|ms'; },
5781 legend: function(state) { return null; },
5782 autoresize: function(state) { return false; },
5783 max_updates_to_recreate: function(state) { return 50; },
5784 track_colors: function(state) { return false; },
5785 pixels_per_point: function(state) { return 15; }
5788 initialize: NETDATA.googleInitialize,
5789 create: NETDATA.googleChartCreate,
5790 update: NETDATA.googleChartUpdate,
5792 setSelection: undefined, //function(state, t) { return true; },
5793 clearSelection: undefined, //function(state) { return true; },
5794 toolboxPanAndZoom: null,
5797 format: function(state) { return 'datatable'; },
5798 options: function(state) { return ''; },
5799 legend: function(state) { return null; },
5800 autoresize: function(state) { return false; },
5801 max_updates_to_recreate: function(state) { return 300; },
5802 track_colors: function(state) { return false; },
5803 pixels_per_point: function(state) { return 4; }
5806 initialize: NETDATA.raphaelInitialize,
5807 create: NETDATA.raphaelChartCreate,
5808 update: NETDATA.raphaelChartUpdate,
5810 setSelection: undefined, // function(state, t) { return true; },
5811 clearSelection: undefined, // function(state) { return true; },
5812 toolboxPanAndZoom: null,
5815 format: function(state) { return 'json'; },
5816 options: function(state) { return ''; },
5817 legend: function(state) { return null; },
5818 autoresize: function(state) { return false; },
5819 max_updates_to_recreate: function(state) { return 5000; },
5820 track_colors: function(state) { return false; },
5821 pixels_per_point: function(state) { return 3; }
5824 initialize: NETDATA.c3Initialize,
5825 create: NETDATA.c3ChartCreate,
5826 update: NETDATA.c3ChartUpdate,
5828 setSelection: undefined, // function(state, t) { return true; },
5829 clearSelection: undefined, // function(state) { return true; },
5830 toolboxPanAndZoom: null,
5833 format: function(state) { return 'csvjsonarray'; },
5834 options: function(state) { return 'milliseconds'; },
5835 legend: function(state) { return null; },
5836 autoresize: function(state) { return false; },
5837 max_updates_to_recreate: function(state) { return 5000; },
5838 track_colors: function(state) { return false; },
5839 pixels_per_point: function(state) { return 15; }
5842 initialize: NETDATA.d3Initialize,
5843 create: NETDATA.d3ChartCreate,
5844 update: NETDATA.d3ChartUpdate,
5846 setSelection: undefined, // function(state, t) { return true; },
5847 clearSelection: undefined, // function(state) { return true; },
5848 toolboxPanAndZoom: null,
5851 format: function(state) { return 'json'; },
5852 options: function(state) { return ''; },
5853 legend: function(state) { return null; },
5854 autoresize: function(state) { return false; },
5855 max_updates_to_recreate: function(state) { return 5000; },
5856 track_colors: function(state) { return false; },
5857 pixels_per_point: function(state) { return 3; }
5860 initialize: NETDATA.easypiechartInitialize,
5861 create: NETDATA.easypiechartChartCreate,
5862 update: NETDATA.easypiechartChartUpdate,
5864 setSelection: NETDATA.easypiechartSetSelection,
5865 clearSelection: NETDATA.easypiechartClearSelection,
5866 toolboxPanAndZoom: null,
5869 format: function(state) { return 'array'; },
5870 options: function(state) { return 'absolute'; },
5871 legend: function(state) { return null; },
5872 autoresize: function(state) { return false; },
5873 max_updates_to_recreate: function(state) { return 5000; },
5874 track_colors: function(state) { return true; },
5875 pixels_per_point: function(state) { return 3; },
5879 initialize: NETDATA.gaugeInitialize,
5880 create: NETDATA.gaugeChartCreate,
5881 update: NETDATA.gaugeChartUpdate,
5883 setSelection: NETDATA.gaugeSetSelection,
5884 clearSelection: NETDATA.gaugeClearSelection,
5885 toolboxPanAndZoom: null,
5888 format: function(state) { return 'array'; },
5889 options: function(state) { return 'absolute'; },
5890 legend: function(state) { return null; },
5891 autoresize: function(state) { return false; },
5892 max_updates_to_recreate: function(state) { return 5000; },
5893 track_colors: function(state) { return true; },
5894 pixels_per_point: function(state) { return 3; },
5899 NETDATA.registerChartLibrary = function(library, url) {
5900 if(NETDATA.options.debug.libraries === true)
5901 console.log("registering chart library: " + library);
5903 NETDATA.chartLibraries[library].url = url;
5904 NETDATA.chartLibraries[library].initialized = true;
5905 NETDATA.chartLibraries[library].enabled = true;
5908 // ----------------------------------------------------------------------------------------------------------------
5909 // Load required JS libraries and CSS
5911 NETDATA.requiredJs = [
5913 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5915 isAlreadyLoaded: function() {
5916 // check if bootstrap is loaded
5917 if(typeof $().emulateTransitionEnd == 'function')
5920 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5928 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5929 isAlreadyLoaded: function() { return false; }
5933 NETDATA.requiredCSS = [
5935 url: NETDATA.themes.current.bootstrap_css,
5936 isAlreadyLoaded: function() {
5937 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5944 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5945 isAlreadyLoaded: function() { return false; }
5948 url: NETDATA.themes.current.dashboard_css,
5949 isAlreadyLoaded: function() { return false; }
5953 NETDATA.loadedRequiredJs = 0;
5954 NETDATA.loadRequiredJs = function(index, callback) {
5955 if(index >= NETDATA.requiredJs.length) {
5956 if(typeof callback === 'function')
5961 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5962 NETDATA.loadedRequiredJs++;
5963 NETDATA.loadRequiredJs(++index, callback);
5967 if(NETDATA.options.debug.main_loop === true)
5968 console.log('loading ' + NETDATA.requiredJs[index].url);
5971 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5975 url: NETDATA.requiredJs[index].url,
5978 xhrFields: { withCredentials: true } // required for the cookie
5981 if(NETDATA.options.debug.main_loop === true)
5982 console.log('loaded ' + NETDATA.requiredJs[index].url);
5985 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5987 .always(function() {
5988 NETDATA.loadedRequiredJs++;
5991 NETDATA.loadRequiredJs(++index, callback);
5995 NETDATA.loadRequiredJs(++index, callback);
5998 NETDATA.loadRequiredCSS = function(index) {
5999 if(index >= NETDATA.requiredCSS.length)
6002 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6003 NETDATA.loadRequiredCSS(++index);
6007 if(NETDATA.options.debug.main_loop === true)
6008 console.log('loading ' + NETDATA.requiredCSS[index].url);
6010 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6011 NETDATA.loadRequiredCSS(++index);
6015 // ----------------------------------------------------------------------------------------------------------------
6016 // Registry of netdata hosts
6019 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6020 chart_div_offset: 100, // give that space above the chart when scrolling to it
6021 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6022 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6024 ms_penalty: 0, // the time penalty of the next alarm
6025 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6026 // if alarms are shown faster than: one per 500ms
6028 notifications: false, // when true, the browser supports notifications (may not be granted though)
6029 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6030 first_notification_id: 0, // the id of the first alarm_log entry for this session
6031 // this is used to prevent CLEAR notifications for past events
6032 // notifications_shown: new Array(),
6034 server: null, // the server to connect to for fetching alarms
6035 current: null, // the list of raised alarms - updated in the background
6036 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6038 notify: function(entry) {
6039 // console.log('alarm ' + entry.unique_id);
6041 if(entry.updated === true) {
6042 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6046 var value = entry.value;
6047 if(NETDATA.alarms.current !== null) {
6048 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6049 if(typeof t !== 'undefined' && entry.status == t.status)
6053 var name = entry.name.replace(/_/g, ' ');
6054 var status = entry.status.toLowerCase();
6055 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6056 var tag = entry.alarm_id;
6057 var icon = 'images/seo-performance-128.png';
6058 var interaction = false;
6062 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6064 switch(entry.status) {
6072 case 'UNINITIALIZED':
6076 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6077 // console.log('alarm ' + entry.unique_id + ' is not current');
6080 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6081 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6084 title = name + ' back to normal';
6085 icon = 'images/check-mark-2-128-green.png'
6086 interaction = false;
6090 if(entry.old_status === 'CRITICAL')
6091 status = 'demoted to ' + entry.status.toLowerCase();
6093 icon = 'images/alert-128-orange.png';
6094 interaction = false;
6098 if(entry.old_status === 'WARNING')
6099 status = 'escalated to ' + entry.status.toLowerCase();
6101 icon = 'images/alert-128-red.png'
6106 console.log('invalid alarm status ' + entry.status);
6111 // cleanup old notifications with the same alarm_id as this one
6112 // FIXME: it does not seem to work on any web browser!
6113 var len = NETDATA.alarms.notifications_shown.length;
6115 var n = NETDATA.alarms.notifications_shown[len];
6116 if(n.data.alarm_id === entry.alarm_id) {
6117 console.log('removing old alarm ' + n.data.unique_id);
6119 // close the notification
6122 // remove it from the array
6123 NETDATA.alarms.notifications_shown.splice(len, 1);
6124 len = NETDATA.alarms.notifications_shown.length;
6131 setTimeout(function() {
6132 // show this notification
6133 // console.log('new notification: ' + title);
6134 var n = new Notification(title, {
6135 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6137 requireInteraction: interaction,
6138 icon: NETDATA.serverDefault + icon,
6142 n.onclick = function(event) {
6143 event.preventDefault();
6144 NETDATA.alarms.onclick(event.target.data);
6148 // NETDATA.alarms.notifications_shown.push(n);
6149 // console.log(entry);
6150 }, NETDATA.alarms.ms_penalty);
6152 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6156 scrollToChart: function(chart_id) {
6157 if(typeof chart_id === 'string') {
6158 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6159 if(typeof offset !== 'undefined') {
6160 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6167 scrollToAlarm: function(alarm) {
6168 if(typeof alarm === 'object') {
6169 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6171 if(ret === true && NETDATA.options.page_is_visible === false)
6173 // 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.');
6178 notifyAll: function() {
6179 // console.log('FETCHING ALARM LOG');
6180 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6181 // console.log('ALARM LOG FETCHED');
6183 if(data === null || typeof data !== 'object') {
6184 console.log('invalid alarms log response');
6188 if(data.length === 0) {
6189 console.log('received empty alarm log');
6193 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6195 data.sort(function(a, b) {
6196 if(a.unique_id > b.unique_id) return -1;
6197 if(a.unique_id < b.unique_id) return 1;
6201 NETDATA.alarms.ms_penalty = 0;
6203 var len = data.length;
6205 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6206 NETDATA.alarms.notify(data[len]);
6209 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6212 NETDATA.alarms.last_notification_id = data[0].unique_id;
6213 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6214 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6218 check_notifications: function() {
6219 // returns true if we should fire 1+ notifications
6221 if(NETDATA.alarms.notifications !== true) {
6222 // console.log('notifications not available');
6226 if(Notification.permission !== 'granted') {
6227 // console.log('notifications not granted');
6231 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6232 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6234 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6235 // console.log('new alarms detected');
6238 //else console.log('no new alarms');
6240 // else console.log('cannot process alarms');
6245 get: function(what, callback) {
6247 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6251 'Cache-Control': 'no-cache, no-store',
6252 'Pragma': 'no-cache'
6254 xhrFields: { withCredentials: true } // required for the cookie
6256 .done(function(data) {
6257 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6258 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6260 if(typeof callback === 'function')
6264 NETDATA.error(415, NETDATA.alarms.server);
6266 if(typeof callback === 'function')
6271 update_forever: function() {
6272 NETDATA.alarms.get('active', function(data) {
6274 NETDATA.alarms.current = data;
6276 if(NETDATA.alarms.check_notifications() === true) {
6277 NETDATA.alarms.notifyAll();
6280 if (typeof NETDATA.alarms.callback === 'function') {
6281 NETDATA.alarms.callback(data);
6284 // Health monitoring is disabled on this netdata
6285 if(data.status === false) return;
6288 setTimeout(NETDATA.alarms.update_forever, 10000);
6292 get_log: function(last_id, callback) {
6293 // console.log('fetching all log after ' + last_id.toString());
6295 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6299 'Cache-Control': 'no-cache, no-store',
6300 'Pragma': 'no-cache'
6302 xhrFields: { withCredentials: true } // required for the cookie
6304 .done(function(data) {
6305 if(typeof callback === 'function')
6309 NETDATA.error(416, NETDATA.alarms.server);
6311 if(typeof callback === 'function')
6317 var host = NETDATA.serverDefault;
6318 while(host.slice(-1) === '/')
6319 host = host.substring(0, host.length - 1);
6320 NETDATA.alarms.server = host;
6322 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6324 if(NETDATA.alarms.onclick === null)
6325 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6327 if(netdataShowAlarms === true) {
6328 NETDATA.alarms.update_forever();
6330 if('Notification' in window) {
6331 // console.log('notifications available');
6332 NETDATA.alarms.notifications = true;
6334 if(Notification.permission === 'default')
6335 Notification.requestPermission();
6341 // ----------------------------------------------------------------------------------------------------------------
6342 // Registry of netdata hosts
6344 NETDATA.registry = {
6345 server: null, // the netdata registry server
6346 person_guid: null, // the unique ID of this browser / user
6347 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6348 hostname: null, // the hostname of the netdata server that served dashboard.js
6349 machines: null, // the user's other URLs
6350 machines_array: null, // the user's other URLs in an array
6353 parsePersonUrls: function(person_urls) {
6354 // console.log(person_urls);
6355 NETDATA.registry.person_urls = person_urls;
6358 NETDATA.registry.machines = {};
6359 NETDATA.registry.machines_array = new Array();
6361 var now = Date.now();
6362 var apu = person_urls;
6365 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6366 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6372 accesses: apu[i][3],
6374 alternate_urls: new Array()
6376 obj.alternate_urls.push(apu[i][1]);
6378 NETDATA.registry.machines[apu[i][0]] = obj;
6379 NETDATA.registry.machines_array.push(obj);
6382 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6384 var pu = NETDATA.registry.machines[apu[i][0]];
6385 if(pu.last_t < apu[i][2]) {
6387 pu.last_t = apu[i][2];
6388 pu.name = apu[i][4];
6390 pu.accesses += apu[i][3];
6391 pu.alternate_urls.push(apu[i][1]);
6396 if(typeof netdataRegistryCallback === 'function')
6397 netdataRegistryCallback(NETDATA.registry.machines_array);
6401 if(netdataRegistry !== true) return;
6403 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6405 NETDATA.registry.server = data.registry;
6406 NETDATA.registry.machine_guid = data.machine_guid;
6407 NETDATA.registry.hostname = data.hostname;
6409 NETDATA.registry.access(2, function (person_urls) {
6410 NETDATA.registry.parsePersonUrls(person_urls);
6417 hello: function(host, callback) {
6418 while(host.slice(-1) === '/')
6419 host = host.substring(0, host.length - 1);
6421 // send HELLO to a netdata server:
6422 // 1. verifies the server is reachable
6423 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6425 url: host + '/api/v1/registry?action=hello',
6429 'Cache-Control': 'no-cache, no-store',
6430 'Pragma': 'no-cache'
6432 xhrFields: { withCredentials: true } // required for the cookie
6434 .done(function(data) {
6435 if(typeof data.status !== 'string' || data.status !== 'ok') {
6436 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6440 if(typeof callback === 'function')
6444 NETDATA.error(407, host);
6446 if(typeof callback === 'function')
6451 access: function(max_redirects, callback) {
6452 // send ACCESS to a netdata registry:
6453 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6454 // 2. it responds with a list of netdata servers we know
6455 // the registry identifies us using a cookie it sets the first time we access it
6456 // the registry may respond with a redirect URL to send us to another registry
6458 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),
6462 'Cache-Control': 'no-cache, no-store',
6463 'Pragma': 'no-cache'
6465 xhrFields: { withCredentials: true } // required for the cookie
6467 .done(function(data) {
6468 var redirect = null;
6469 if(typeof data.registry === 'string')
6470 redirect = data.registry;
6472 if(typeof data.status !== 'string' || data.status !== 'ok') {
6473 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6478 if(redirect !== null && max_redirects > 0) {
6479 NETDATA.registry.server = redirect;
6480 NETDATA.registry.access(max_redirects - 1, callback);
6483 if(typeof callback === 'function')
6488 if(typeof data.person_guid === 'string')
6489 NETDATA.registry.person_guid = data.person_guid;
6491 if(typeof callback === 'function')
6492 callback(data.urls);
6496 NETDATA.error(410, NETDATA.registry.server);
6498 if(typeof callback === 'function')
6503 delete: function(delete_url, callback) {
6504 // send DELETE to a netdata registry:
6506 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),
6510 'Cache-Control': 'no-cache, no-store',
6511 'Pragma': 'no-cache'
6513 xhrFields: { withCredentials: true } // required for the cookie
6515 .done(function(data) {
6516 if(typeof data.status !== 'string' || data.status !== 'ok') {
6517 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6521 if(typeof callback === 'function')
6525 NETDATA.error(412, NETDATA.registry.server);
6527 if(typeof callback === 'function')
6532 search: function(machine_guid, callback) {
6533 // SEARCH for the URLs of a machine:
6535 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,
6539 'Cache-Control': 'no-cache, no-store',
6540 'Pragma': 'no-cache'
6542 xhrFields: { withCredentials: true } // required for the cookie
6544 .done(function(data) {
6545 if(typeof data.status !== 'string' || data.status !== 'ok') {
6546 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6550 if(typeof callback === 'function')
6554 NETDATA.error(418, NETDATA.registry.server);
6556 if(typeof callback === 'function')
6561 switch: function(new_person_guid, callback) {
6564 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,
6568 'Cache-Control': 'no-cache, no-store',
6569 'Pragma': 'no-cache'
6571 xhrFields: { withCredentials: true } // required for the cookie
6573 .done(function(data) {
6574 if(typeof data.status !== 'string' || data.status !== 'ok') {
6575 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6579 if(typeof callback === 'function')
6583 NETDATA.error(414, NETDATA.registry.server);
6585 if(typeof callback === 'function')
6591 // ----------------------------------------------------------------------------------------------------------------
6594 if(typeof netdataPrepCallback === 'function')
6595 netdataPrepCallback();
6597 NETDATA.errorReset();
6598 NETDATA.loadRequiredCSS(0);
6600 NETDATA._loadjQuery(function() {
6601 NETDATA.loadRequiredJs(0, function() {
6602 if(typeof $().emulateTransitionEnd !== 'function') {
6603 // bootstrap is not available
6604 NETDATA.options.current.show_help = false;
6607 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6608 if(NETDATA.options.debug.main_loop === true)
6609 console.log('starting chart refresh thread');
6615 })(window, document);