1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
33 var NETDATA = window.NETDATA || {};
35 (function(window, document, undefined) {
36 // ------------------------------------------------------------------------
37 // compatibility fixes
39 // fix IE issue with console
40 if(!window.console) { window.console = { log: function(){} }; }
42 // if string.endsWith is not defined, define it
43 if(typeof String.prototype.endsWith !== 'function') {
44 String.prototype.endsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(-s.length) === s;
50 // if string.startsWith is not defined, define it
51 if(typeof String.prototype.startsWith !== 'function') {
52 String.prototype.startsWith = function(s) {
53 if(s.length > this.length) return false;
54 return this.slice(s.length) === s;
58 NETDATA.name2id = function(s) {
67 // ----------------------------------------------------------------------------------------------------------------
68 // Detect the netdata server
70 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
71 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
72 NETDATA._scriptSource = function() {
75 if(typeof document.currentScript !== 'undefined') {
76 script = document.currentScript;
79 var all_scripts = document.getElementsByTagName('script');
80 script = all_scripts[all_scripts.length - 1];
83 if (typeof script.getAttribute.length !== 'undefined')
86 script = script.getAttribute('src', -1);
91 if(typeof netdataServer !== 'undefined')
92 NETDATA.serverDefault = netdataServer;
94 var s = NETDATA._scriptSource();
95 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97 console.log('WARNING: Cannot detect the URL of the netdata server.');
98 NETDATA.serverDefault = null;
102 if(NETDATA.serverDefault === null)
103 NETDATA.serverDefault = '';
104 else if(NETDATA.serverDefault.slice(-1) !== '/')
105 NETDATA.serverDefault += '/';
107 // default URLs for all the external files we need
108 // make them RELATIVE so that the whole thing can also be
109 // installed under a web server
110 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
111 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
112 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
113 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
114 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
115 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
116 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
117 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
118 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
119 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
120 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
121 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
122 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
123 NETDATA.google_js = 'https://www.google.com/jsapi';
127 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
128 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
129 background: '#FFFFFF',
130 foreground: '#000000',
133 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
134 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
135 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
136 '#329262', '#3B3EAC' ],
137 easypiechart_track: '#f0f0f0',
138 easypiechart_scale: '#dfe0e0',
139 gauge_pointer: '#C0C0C0',
140 gauge_stroke: '#F0F0F0',
141 gauge_gradient: false
144 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
145 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
146 background: '#272b30',
147 foreground: '#C8C8C8',
150 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
151 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
152 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
153 '#a6a479', '#a66da8' ],
155 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
156 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
157 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
158 '#329262', '#3B3EFF' ],
159 easypiechart_track: '#373b40',
160 easypiechart_scale: '#373b40',
161 gauge_pointer: '#474b50',
162 gauge_stroke: '#373b40',
163 gauge_gradient: false
167 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
168 NETDATA.themes.current = NETDATA.themes[netdataTheme];
170 NETDATA.themes.current = NETDATA.themes.white;
172 NETDATA.colors = NETDATA.themes.current.colors;
174 // these are the colors Google Charts are using
175 // we have them here to attempt emulate their look and feel on the other chart libraries
176 // http://there4.io/2012/05/02/google-chart-color-list/
177 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
178 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
179 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181 // an alternative set
182 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
183 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
184 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186 if(typeof netdataShowHelp === 'undefined')
187 netdataShowHelp = true;
189 if(typeof netdataShowAlarms === 'undefined')
190 netdataShowAlarms = false;
192 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
193 netdataRegistryAfterMs = 1500;
195 if(typeof netdataRegistry === 'undefined') {
196 // backward compatibility
197 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
198 netdataRegistry = true;
200 netdataRegistry = false;
202 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
203 netdataRegistry = true;
205 // ----------------------------------------------------------------------------------------------------------------
206 // the defaults for all charts
208 // if the user does not specify any of these, the following will be used
210 NETDATA.chartDefaults = {
211 host: NETDATA.serverDefault, // the server to get data from
212 width: '100%', // the chart width - can be null
213 height: '100%', // the chart height - can be null
214 min_width: null, // the chart minimum width - can be null
215 library: 'dygraph', // the graphing library to use
216 method: 'average', // the grouping method
217 before: 0, // panning
218 after: -600, // panning
219 pixels_per_point: 1, // the detail of the chart
220 fill_luminance: 0.8 // luminance of colors in solit areas
223 // ----------------------------------------------------------------------------------------------------------------
227 pauseCallback: null, // a callback when we are really paused
229 pause: false, // when enabled we don't auto-refresh the charts
231 targets: null, // an array of all the state objects that are
232 // currently active (independently of their
233 // viewport visibility)
235 updated_dom: true, // when true, the DOM has been updated with
236 // new elements we have to check.
238 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
239 // rendering charts continiously.
240 // used with .current.fast_render_timeframe
242 page_is_visible: true, // when true, this page is visible
244 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
245 // auto-refresher for some time (when a chart is
246 // performing pan or zoom, we need to stop refreshing
247 // all other charts, to have the maximum speed for
248 // rendering the chart that is panned or zoomed).
249 // Used with .current.global_pan_sync_time
251 last_resized: Date.now(), // the timestamp of the last resize request
253 last_page_scroll: 0, // the timestamp the last time the page was scrolled
255 // the current profile
256 // we may have many...
258 pixels_per_point: 1, // the minimum pixels per point for all charts
259 // increase this to speed javascript up
260 // each chart library has its own limit too
261 // the max of this and the chart library is used
262 // the final is calculated every time, so a change
263 // here will have immediate effect on the next chart
266 idle_between_charts: 100, // ms - how much time to wait between chart updates
268 fast_render_timeframe: 200, // ms - render continously until this time of continious
269 // rendering has been reached
270 // this setting is used to make it render e.g. 10
271 // charts at once, sleep idle_between_charts time
272 // and continue for another 10 charts.
274 idle_between_loops: 500, // ms - if all charts have been updated, wait this
275 // time before starting again.
277 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
279 idle_lost_focus: 500, // ms - when the window does not have focus, check
280 // if focus has been regained, every this time
282 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
283 // autorefreshing of charts is paused for this amount
286 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
287 // of time before setting up synchronized selections
290 sync_selection: true, // enable or disable selection sync
292 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
294 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
296 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298 update_only_visible: true, // enable or disable visibility management
300 parallel_refresher: true, // enable parallel refresh of charts
302 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304 destroy_on_hide: false, // destroy charts when they are not visible
306 show_help: netdataShowHelp, // when enabled the charts will show some help
307 show_help_delay_show_ms: 500,
308 show_help_delay_hide_ms: 0,
310 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
313 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
315 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
317 smooth_plot: true, // enable smooth plot, where possible
319 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321 color_fill_opacity_line: 1.0,
322 color_fill_opacity_area: 0.2,
323 color_fill_opacity_stacked: 0.8,
325 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
326 pan_and_zoom_factor_multiplier_control: 2.0,
327 pan_and_zoom_factor_multiplier_shift: 3.0,
328 pan_and_zoom_factor_multiplier_alt: 4.0,
330 abort_ajax_on_scroll: false, // kill pending ajax page scroll
331 async_on_scroll: false, // sync/async onscroll handler
332 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
336 setOptionCallback: function() { ; }
344 chart_data_url: false,
345 chart_errors: false, // FIXME
353 NETDATA.statistics = {
356 refreshes_active_max: 0
360 // ----------------------------------------------------------------------------------------------------------------
361 // local storage options
363 NETDATA.localStorage = {
366 callback: {} // only used for resetting back to defaults
369 NETDATA.localStorageGet = function(key, def, callback) {
372 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
373 NETDATA.localStorage.default[key.toString()] = def;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
379 // console.log('localStorage: loading "' + key.toString() + '"');
380 ret = localStorage.getItem(key.toString());
381 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
382 if(ret === null || ret === 'undefined') {
383 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
384 localStorage.setItem(key.toString(), JSON.stringify(def));
388 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
389 ret = JSON.parse(ret);
390 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
394 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
399 if(typeof ret === 'undefined' || ret === 'undefined') {
400 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
404 NETDATA.localStorage.current[key.toString()] = ret;
408 NETDATA.localStorageSet = function(key, value, callback) {
409 if(typeof value === 'undefined' || value === 'undefined') {
410 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
413 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
414 NETDATA.localStorage.default[key.toString()] = value;
415 NETDATA.localStorage.current[key.toString()] = value;
416 NETDATA.localStorage.callback[key.toString()] = callback;
419 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
420 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(value));
425 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
429 NETDATA.localStorage.current[key.toString()] = value;
433 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
435 if(typeof obj[i] === 'object') {
436 //console.log('object ' + prefix + '.' + i.toString());
437 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
441 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
445 NETDATA.setOption = function(key, value) {
446 if(key.toString() === 'setOptionCallback') {
447 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
448 NETDATA.options.current[key.toString()] = value;
449 NETDATA.options.current.setOptionCallback();
452 else if(NETDATA.options.current[key.toString()] !== value) {
453 var name = 'options.' + key.toString();
455 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
456 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
458 //console.log(NETDATA.localStorage);
459 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
460 //console.log(NETDATA.options);
461 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
463 if(typeof NETDATA.options.current.setOptionCallback === 'function')
464 NETDATA.options.current.setOptionCallback();
470 NETDATA.getOption = function(key) {
471 return NETDATA.options.current[key.toString()];
474 // read settings from local storage
475 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
477 // always start with this option enabled.
478 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
480 NETDATA.resetOptions = function() {
481 for(var i in NETDATA.localStorage.default) {
482 var a = i.split('.');
484 if(a[0] === 'options') {
485 if(a[1] === 'setOptionCallback') continue;
486 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
487 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
489 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
491 else if(a[0] === 'chart_heights') {
492 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
493 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
499 // ----------------------------------------------------------------------------------------------------------------
501 if(NETDATA.options.debug.main_loop === true)
502 console.log('welcome to NETDATA');
504 NETDATA.onresizeCallback = null;
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
509 if(typeof NETDATA.onresizeCallback === 'function')
510 NETDATA.onresizeCallback();
513 NETDATA.onscroll_updater_count = 0;
514 NETDATA.onscroll_updater_running = false;
515 NETDATA.onscroll_updater_last_run = 0;
516 NETDATA.onscroll_updater_watchdog = null;
517 NETDATA.onscroll_updater_max_duration = 0;
518 NETDATA.onscroll_updater_above_threshold_count = 0;
519 NETDATA.onscroll_updater = function() {
520 NETDATA.onscroll_updater_running = true;
521 NETDATA.onscroll_updater_count++;
522 var start = Date.now();
524 var targets = NETDATA.options.targets;
525 var len = targets.length;
527 // when the user scrolls he sees that we have
528 // hidden all the not-visible charts
529 // using this little function we try to switch
530 // the charts back to visible quickly
533 if(NETDATA.options.abort_ajax_on_scroll === true) {
534 // we have to cancel pending requests too
537 if (targets[len]._updating === true) {
538 if (typeof targets[len].xhr !== 'undefined') {
539 targets[len].xhr.abort();
540 targets[len].running = false;
541 targets[len]._updating = false;
543 targets[len].isVisible();
548 // just find which chart is visible
551 targets[len].isVisible();
554 var end = Date.now();
555 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
557 if(NETDATA.options.current.async_on_scroll === false) {
558 var dt = end - start;
559 if(dt > NETDATA.onscroll_updater_max_duration) {
560 // console.log('max onscroll event handler duration increased to ' + dt);
561 NETDATA.onscroll_updater_max_duration = dt;
564 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
565 // console.log('slow: ' + dt);
566 NETDATA.onscroll_updater_above_threshold_count++;
568 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
569 NETDATA.setOption('async_on_scroll', true);
570 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
575 NETDATA.onscroll_updater_last_run = start;
576 NETDATA.onscroll_updater_running = false;
579 NETDATA.onscroll = function() {
580 // console.log('onscroll');
582 NETDATA.options.last_page_scroll = Date.now();
583 NETDATA.options.auto_refresher_stop_until = 0;
585 if(NETDATA.options.targets === null) return;
587 if(NETDATA.options.current.async_on_scroll === true) {
589 if(NETDATA.onscroll_updater_running === false) {
590 NETDATA.onscroll_updater_running = true;
591 setTimeout(NETDATA.onscroll_updater, 0);
594 if(NETDATA.onscroll_updater_watchdog !== null)
595 clearTimeout(NETDATA.onscroll_updater_watchdog);
597 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
598 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
599 // console.log('watchdog');
600 NETDATA.onscroll_updater();
603 NETDATA.onscroll_updater_watchdog = null;
609 NETDATA.onscroll_updater();
613 window.onresize = NETDATA.onresize;
614 window.onscroll = NETDATA.onscroll;
616 // ----------------------------------------------------------------------------------------------------------------
619 NETDATA.errorCodes = {
620 100: { message: "Cannot load chart library", alert: true },
621 101: { message: "Cannot load jQuery", alert: true },
622 402: { message: "Chart library not found", alert: false },
623 403: { message: "Chart library not enabled/is failed", alert: false },
624 404: { message: "Chart not found", alert: false },
625 405: { message: "Cannot download charts index from server", alert: true },
626 406: { message: "Invalid charts index downloaded from server", alert: true },
627 407: { message: "Cannot HELLO netdata server", alert: false },
628 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
629 409: { message: "Cannot ACCESS netdata registry", alert: false },
630 410: { message: "Netdata registry ACCESS failed", alert: false },
631 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
632 412: { message: "Netdata registry DELETE failed", alert: false },
633 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
634 414: { message: "Netdata registry SWITCH failed", alert: false },
635 415: { message: "Netdata alarms download failed", alert: false },
636 416: { message: "Netdata alarms log download failed", alert: false },
637 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
638 418: { message: "Netdata registry SEARCH failed", alert: false }
640 NETDATA.errorLast = {
646 NETDATA.error = function(code, msg) {
647 NETDATA.errorLast.code = code;
648 NETDATA.errorLast.message = msg;
649 NETDATA.errorLast.datetime = Date.now();
651 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
654 if(typeof netdataErrorCallback === 'function') {
655 ret = netdataErrorCallback('system', code, msg);
658 if(ret && NETDATA.errorCodes[code].alert)
659 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
662 NETDATA.errorReset = function() {
663 NETDATA.errorLast.code = 0;
664 NETDATA.errorLast.message = "You are doing fine!";
665 NETDATA.errorLast.datetime = 0;
668 // ----------------------------------------------------------------------------------------------------------------
669 // commonMin & commonMax
671 NETDATA.commonMin = {
675 get: function(state) {
676 if(typeof state.__commonMin === 'undefined') {
677 // get the commonMin setting
678 var self = $(state.element);
679 state.__commonMin = self.data('common-min') || null;
682 var min = state.data.min;
683 var name = state.__commonMin;
686 // we don't need commonMin
687 //state.log('no need for commonMin');
691 var t = this.keys[name];
692 if(typeof t === 'undefined') {
694 this.keys[name] = {};
698 var uuid = state.uuid;
699 if(typeof t[uuid] !== 'undefined') {
700 if(t[uuid] === min) {
701 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
702 return this.latest[name];
704 else if(min < this.latest[name]) {
705 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
707 this.latest[name] = min;
715 // find the common min
718 if(t[i] < m) m = t[i];
720 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
721 this.latest[name] = m;
726 NETDATA.commonMax = {
730 get: function(state) {
731 if(typeof state.__commonMax === 'undefined') {
732 // get the commonMax setting
733 var self = $(state.element);
734 state.__commonMax = self.data('common-max') || null;
737 var max = state.data.max;
738 var name = state.__commonMax;
741 // we don't need commonMax
742 //state.log('no need for commonMax');
746 var t = this.keys[name];
747 if(typeof t === 'undefined') {
749 this.keys[name] = {};
753 var uuid = state.uuid;
754 if(typeof t[uuid] !== 'undefined') {
755 if(t[uuid] === max) {
756 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
757 return this.latest[name];
759 else if(max > this.latest[name]) {
760 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
762 this.latest[name] = max;
770 // find the common max
773 if(t[i] > m) m = t[i];
775 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
776 this.latest[name] = m;
781 // ----------------------------------------------------------------------------------------------------------------
784 // When multiple charts need the same chart, we avoid downloading it
785 // multiple times (and having it in browser memory multiple time)
786 // by using this registry.
788 // Every time we download a chart definition, we save it here with .add()
789 // Then we try to get it back with .get(). If that fails, we download it.
791 NETDATA.chartRegistry = {
794 fixid: function(id) {
795 return id.replace(/:/g, "_").replace(/\//g, "_");
798 add: function(host, id, data) {
799 host = this.fixid(host);
802 if(typeof this.charts[host] === 'undefined')
803 this.charts[host] = {};
805 //console.log('added ' + host + '/' + id);
806 this.charts[host][id] = data;
809 get: function(host, id) {
810 host = this.fixid(host);
813 if(typeof this.charts[host] === 'undefined')
816 if(typeof this.charts[host][id] === 'undefined')
819 //console.log('cached ' + host + '/' + id);
820 return this.charts[host][id];
823 downloadAll: function(host, callback) {
824 while(host.slice(-1) === '/')
825 host = host.substring(0, host.length - 1);
830 url: host + '/api/v1/charts',
833 xhrFields: { withCredentials: true } // required for the cookie
835 .done(function(data) {
837 var h = NETDATA.chartRegistry.fixid(host);
838 self.charts[h] = data.charts;
840 else NETDATA.error(406, host + '/api/v1/charts');
842 if(typeof callback === 'function')
846 NETDATA.error(405, host + '/api/v1/charts');
848 if(typeof callback === 'function')
854 // ----------------------------------------------------------------------------------------------------------------
855 // Global Pan and Zoom on charts
857 // Using this structure are synchronize all the charts, so that
858 // when you pan or zoom one, all others are automatically refreshed
859 // to the same timespan.
861 NETDATA.globalPanAndZoom = {
862 seq: 0, // timestamp ms
863 // every time a chart is panned or zoomed
864 // we set the timestamp here
865 // then we use it as a sequence number
866 // to find if other charts are syncronized
869 master: null, // the master chart (state), to which all others
872 force_before_ms: null, // the timespan to sync all other charts
873 force_after_ms: null,
878 setMaster: function(state, after, before) {
879 if(NETDATA.options.current.sync_pan_and_zoom === false)
882 if(this.master !== null && this.master !== state)
883 this.master.resetChart(true, true);
885 var now = Date.now();
888 this.force_after_ms = after;
889 this.force_before_ms = before;
890 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
892 if(typeof this.callback === 'function')
893 this.callback(true, after, before);
897 clearMaster: function() {
898 if(this.master !== null) {
899 var st = this.master;
906 this.force_after_ms = null;
907 this.force_before_ms = null;
908 NETDATA.options.auto_refresher_stop_until = 0;
910 if(typeof this.callback === 'function')
911 this.callback(false, 0, 0);
914 // is the given state the master of the global
915 // pan and zoom sync?
916 isMaster: function(state) {
917 if(this.master === state) return true;
921 // are we currently have a global pan and zoom sync?
922 isActive: function() {
923 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
927 // check if a chart, other than the master
928 // needs to be refreshed, due to the global pan and zoom
929 shouldBeAutoRefreshed: function(state) {
930 if(this.master === null || this.seq === 0)
933 //if(state.needsRecreation())
936 if(state.tm.pan_and_zoom_seq === this.seq)
943 // ----------------------------------------------------------------------------------------------------------------
944 // dimensions selection
947 // move color assignment to dimensions, here
949 dimensionStatus = function(parent, label, name_div, value_div, color) {
950 this.enabled = false;
951 this.parent = parent;
953 this.name_div = null;
954 this.value_div = null;
955 this.color = NETDATA.themes.current.foreground;
957 if(parent.unselected_count === 0)
958 this.selected = true;
960 this.selected = false;
962 this.setOptions(name_div, value_div, color);
965 dimensionStatus.prototype.invalidate = function() {
966 this.name_div = null;
967 this.value_div = null;
968 this.enabled = false;
971 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
974 if(this.name_div != name_div) {
975 this.name_div = name_div;
976 this.name_div.title = this.label;
977 this.name_div.style.color = this.color;
978 if(this.selected === false)
979 this.name_div.className = 'netdata-legend-name not-selected';
981 this.name_div.className = 'netdata-legend-name selected';
984 if(this.value_div != value_div) {
985 this.value_div = value_div;
986 this.value_div.title = this.label;
987 this.value_div.style.color = this.color;
988 if(this.selected === false)
989 this.value_div.className = 'netdata-legend-value not-selected';
991 this.value_div.className = 'netdata-legend-value selected';
998 dimensionStatus.prototype.setHandler = function() {
999 if(this.enabled === false) return;
1003 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1004 this.name_div.onclick = this.value_div.onclick = function(e) {
1006 if(ds.isSelected()) {
1008 if(e.shiftKey === true || e.ctrlKey === true) {
1009 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1012 if(ds.parent.countSelected() === 0)
1013 ds.parent.selectAll();
1016 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1017 if(ds.parent.countSelected() === 1) {
1018 ds.parent.selectAll();
1021 ds.parent.selectNone();
1027 // this is not selected
1028 if(e.shiftKey === true || e.ctrlKey === true) {
1029 // control or shift key is pressed -> select this too
1033 // no key is pressed -> select only this
1034 ds.parent.selectNone();
1039 ds.parent.state.redrawChart();
1043 dimensionStatus.prototype.select = function() {
1044 if(this.enabled === false) return;
1046 this.name_div.className = 'netdata-legend-name selected';
1047 this.value_div.className = 'netdata-legend-value selected';
1048 this.selected = true;
1051 dimensionStatus.prototype.unselect = function() {
1052 if(this.enabled === false) return;
1054 this.name_div.className = 'netdata-legend-name not-selected';
1055 this.value_div.className = 'netdata-legend-value hidden';
1056 this.selected = false;
1059 dimensionStatus.prototype.isSelected = function() {
1060 return(this.enabled === true && this.selected === true);
1063 // ----------------------------------------------------------------------------------------------------------------
1065 dimensionsVisibility = function(state) {
1068 this.dimensions = {};
1069 this.selected_count = 0;
1070 this.unselected_count = 0;
1073 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1074 if(typeof this.dimensions[label] === 'undefined') {
1076 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1079 this.dimensions[label].setOptions(name_div, value_div, color);
1081 return this.dimensions[label];
1084 dimensionsVisibility.prototype.dimensionGet = function(label) {
1085 return this.dimensions[label];
1088 dimensionsVisibility.prototype.invalidateAll = function() {
1089 for(var d in this.dimensions)
1090 this.dimensions[d].invalidate();
1093 dimensionsVisibility.prototype.selectAll = function() {
1094 for(var d in this.dimensions)
1095 this.dimensions[d].select();
1098 dimensionsVisibility.prototype.countSelected = function() {
1100 for(var d in this.dimensions)
1101 if(this.dimensions[d].isSelected()) i++;
1106 dimensionsVisibility.prototype.selectNone = function() {
1107 for(var d in this.dimensions)
1108 this.dimensions[d].unselect();
1111 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1112 var ret = new Array();
1113 this.selected_count = 0;
1114 this.unselected_count = 0;
1116 var len = array.length;
1118 var ds = this.dimensions[array[len]];
1119 if(typeof ds === 'undefined') {
1120 // console.log(array[i] + ' is not found');
1123 else if(ds.isSelected()) {
1125 this.selected_count++;
1129 this.unselected_count++;
1133 if(this.selected_count === 0 && this.unselected_count !== 0) {
1135 return this.selected2BooleanArray(array);
1142 // ----------------------------------------------------------------------------------------------------------------
1143 // global selection sync
1145 NETDATA.globalSelectionSync = {
1147 dont_sync_before: 0,
1152 if(this.state !== null)
1153 this.state.globalSelectionSyncStop();
1157 if(this.state !== null) {
1158 this.state.globalSelectionSyncDelay();
1163 // ----------------------------------------------------------------------------------------------------------------
1164 // http://wilsonpage.co.uk/preventing-layout-thrashing/
1166 NETDATA.noLayoutTrashing = {
1170 add: function(callback) {
1171 // console.log('adding...');
1172 this.callbacks.unshift(callback);
1174 if(this.set === false) {
1178 window.requestAnimationFrame(function() {
1179 var len = that.callbacks.length;
1180 // console.log('running... ' + len.toString());
1182 that.callbacks[len]();
1185 that.callbacks = new Array();
1192 // ----------------------------------------------------------------------------------------------------------------
1193 // Our state object, where all per-chart values are stored
1195 chartState = function(element) {
1196 var self = $(element);
1197 this.element = element;
1200 // all private functions should use 'that', instead of 'this'
1203 /* error() - private
1204 * show an error instead of the chart
1206 var error = function(msg) {
1209 if(typeof netdataErrorCallback === 'function') {
1210 ret = netdataErrorCallback('chart', that.id, msg);
1214 that.element.innerHTML = that.id + ': ' + msg;
1215 that.enabled = false;
1216 that.current = that.pan;
1220 // GUID - a unique identifier for the chart
1221 this.uuid = NETDATA.guid();
1223 // string - the name of chart
1224 this.id = self.data('netdata');
1226 // string - the key for localStorage settings
1227 this.settings_id = self.data('id') || null;
1229 // the user given dimensions of the element
1230 this.width = self.data('width') || NETDATA.chartDefaults.width;
1231 this.height = self.data('height') || NETDATA.chartDefaults.height;
1232 this.height_original = this.height;
1234 if(this.settings_id !== null) {
1235 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1236 // this is the callback that will be called
1237 // if and when the user resets all localStorage variables
1238 // to their defaults
1240 resizeChartToHeight(height);
1244 // string - the netdata server URL, without any path
1245 this.host = self.data('host') || NETDATA.chartDefaults.host;
1247 // make sure the host does not end with /
1248 // all netdata API requests use absolute paths
1249 while(this.host.slice(-1) === '/')
1250 this.host = this.host.substring(0, this.host.length - 1);
1252 // string - the grouping method requested by the user
1253 this.method = self.data('method') || NETDATA.chartDefaults.method;
1255 // the time-range requested by the user
1256 this.after = self.data('after') || NETDATA.chartDefaults.after;
1257 this.before = self.data('before') || NETDATA.chartDefaults.before;
1259 // the pixels per point requested by the user
1260 this.pixels_per_point = self.data('pixels-per-point') || 1;
1261 this.points = self.data('points') || null;
1263 // the dimensions requested by the user
1264 this.dimensions = self.data('dimensions') || null;
1266 // the chart library requested by the user
1267 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1269 // how many retries we have made to load chart data from the server
1270 this.retries_on_data_failures = 0;
1272 // object - the chart library used
1273 this.library = null;
1277 this.colors_assigned = {};
1278 this.colors_available = null;
1280 // the element already created by the user
1281 this.element_message = null;
1283 // the element with the chart
1284 this.element_chart = null;
1286 // the element with the legend of the chart (if created by us)
1287 this.element_legend = null;
1288 this.element_legend_childs = {
1293 perfect_scroller: null, // the container to apply perfect scroller to
1297 this.chart_url = null; // string - the url to download chart info
1298 this.chart = null; // object - the chart as downloaded from the server
1300 this.title = self.data('title') || null; // the title of the chart
1301 this.units = self.data('units') || null; // the units of the chart dimensions
1302 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1303 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1305 this.running = false; // boolean - true when the chart is being refreshed now
1306 this.validated = false; // boolean - has the chart been validated?
1307 this.enabled = true; // boolean - is the chart enabled for refresh?
1308 this.paused = false; // boolean - is the chart paused for any reason?
1309 this.selected = false; // boolean - is the chart shown a selection?
1310 this.debug = false; // boolean - console.log() debug info about this chart
1312 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1313 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1314 this.requested_after = null; // milliseconds - the timestamp of the request after param
1315 this.requested_before = null; // milliseconds - the timestamp of the request before param
1316 this.requested_padding = null;
1317 this.view_after = 0;
1318 this.view_before = 0;
1320 this.value_decimal_detail = -1;
1322 var d = self.data('decimal-digits');
1323 if(typeof d === 'number') {
1324 this.value_decimal_detail = 1;
1326 this.value_decimal_detail *= 10;
1333 force_update_at: 0, // the timestamp to force the update at
1334 force_before_ms: null,
1335 force_after_ms: null
1340 force_update_at: 0, // the timestamp to force the update at
1341 force_before_ms: null,
1342 force_after_ms: null
1347 force_update_at: 0, // the timestamp to force the update at
1348 force_before_ms: null,
1349 force_after_ms: null
1352 // this is a pointer to one of the sub-classes below
1354 this.current = this.auto;
1356 // check the requested library is available
1357 // we don't initialize it here - it will be initialized when
1358 // this chart will be first used
1359 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1360 NETDATA.error(402, that.library_name);
1361 error('chart library "' + that.library_name + '" is not found');
1364 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1365 NETDATA.error(403, that.library_name);
1366 error('chart library "' + that.library_name + '" is not enabled');
1370 that.library = NETDATA.chartLibraries[that.library_name];
1372 // milliseconds - the time the last refresh took
1373 this.refresh_dt_ms = 0;
1375 // if we need to report the rendering speed
1376 // find the element that needs to be updated
1377 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1379 if(refresh_dt_element_name !== null)
1380 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1382 this.refresh_dt_element = null;
1384 this.dimensions_visibility = new dimensionsVisibility(this);
1386 this._updating = false;
1388 // ============================================================================================================
1389 // PRIVATE FUNCTIONS
1391 var createDOM = function() {
1392 if(that.enabled === false) return;
1394 if(that.element_message !== null) that.element_message.innerHTML = '';
1395 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1396 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1398 that.element.innerHTML = '';
1400 that.element_message = document.createElement('div');
1401 that.element_message.className = 'netdata-message icon hidden';
1402 that.element.appendChild(that.element_message);
1404 that.element_chart = document.createElement('div');
1405 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1406 that.element.appendChild(that.element_chart);
1408 if(that.hasLegend() === true) {
1409 that.element.className = "netdata-container-with-legend";
1410 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1412 that.element_legend = document.createElement('div');
1413 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1414 that.element.appendChild(that.element_legend);
1417 that.element.className = "netdata-container";
1418 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1420 that.element_legend = null;
1422 that.element_legend_childs.series = null;
1424 NETDATA.noLayoutTrashing.add(function() {
1425 if(typeof(that.width) === 'string')
1426 $(that.element).css('width', that.width);
1427 else if(typeof(that.width) === 'number')
1428 $(that.element).css('width', that.width + 'px');
1430 if(typeof(that.library.aspect_ratio) === 'undefined') {
1431 if(typeof(that.height) === 'string')
1432 that.element.style.height = that.height;
1433 else if(typeof(that.height) === 'number')
1434 that.element.style.height = that.height.toString() + 'px';
1437 var w = that.element.offsetWidth;
1438 if(w === null || w === 0) {
1439 // the div is hidden
1440 // this will resize the chart when next viewed
1441 that.tm.last_resized = 0;
1444 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1448 if(NETDATA.chartDefaults.min_width !== null)
1449 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1451 that.tm.last_dom_created = Date.now();
1457 * initialize state variables
1458 * destroy all (possibly) created state elements
1459 * create the basic DOM for a chart
1461 var init = function() {
1462 if(that.enabled === false) return;
1464 that.paused = false;
1465 that.selected = false;
1467 that.chart_created = false; // boolean - is the library.create() been called?
1468 that.updates_counter = 0; // numeric - the number of refreshes made so far
1469 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1470 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1473 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1474 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1475 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1477 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1478 last_updated: 0, // the timestamp the chart last updated with data
1479 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1481 // Used with NETDATA.globalPanAndZoom.seq
1482 last_visible_check: 0, // the time we last checked if it is visible
1483 last_resized: 0, // the time the chart was resized
1484 last_hidden: 0, // the time the chart was hidden
1485 last_unhidden: 0, // the time the chart was unhidden
1486 last_autorefreshed: 0 // the time the chart was last refreshed
1489 that.data = null; // the last data as downloaded from the netdata server
1490 that.data_url = 'invalid://'; // string - the last url used to update the chart
1491 that.data_points = 0; // number - the number of points returned from netdata
1492 that.data_after = 0; // milliseconds - the first timestamp of the data
1493 that.data_before = 0; // milliseconds - the last timestamp of the data
1494 that.data_update_every = 0; // milliseconds - the frequency to update the data
1496 that.tm.last_initialized = Date.now();
1499 that.setMode('auto');
1502 var maxMessageFontSize = function() {
1503 var screenHeight = screen.height;
1504 var el = that.element;
1506 // normally we want a font size, as tall as the element
1507 var h = el.clientHeight;
1509 // but give it some air, 20% let's say, or 5 pixels min
1510 var lost = Math.max(h * 0.2, 5);
1513 // center the text, vertically
1514 var paddingTop = (lost - 5) / 2;
1516 // but check the width too
1517 // it should fit 10 characters in it
1518 var w = el.clientWidth / 10;
1520 paddingTop += (h - w) / 2;
1524 // and don't make it too huge
1525 // 5% of the screen size is good
1526 if(h > screenHeight / 20) {
1527 paddingTop += (h - (screenHeight / 20)) / 2;
1528 h = screenHeight / 20;
1532 that.element_message.style.fontSize = h.toString() + 'px';
1533 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1536 var showMessageIcon = function(icon) {
1537 NETDATA.noLayoutTrashing.add(function() {
1538 that.element_message.innerHTML = icon;
1539 maxMessageFontSize();
1540 $(that.element_message).removeClass('hidden');
1541 that.___messageHidden___ = undefined;
1545 var hideMessage = function() {
1546 if(typeof that.___messageHidden___ === 'undefined') {
1547 that.___messageHidden___ = true;
1548 $(that.element_message).addClass('hidden');
1552 var showRendering = function() {
1554 if(that.chart !== null) {
1555 if(that.chart.chart_type === 'line')
1556 icon = '<i class="fa fa-line-chart"></i>';
1558 icon = '<i class="fa fa-area-chart"></i>';
1561 icon = '<i class="fa fa-area-chart"></i>';
1563 showMessageIcon(icon + ' netdata');
1566 var showLoading = function() {
1567 if(that.chart_created === false) {
1568 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1574 var isHidden = function() {
1575 if(typeof that.___chartIsHidden___ !== 'undefined')
1581 // hide the chart, when it is not visible - called from isVisible()
1582 var hideChart = function() {
1583 // hide it, if it is not already hidden
1584 if(isHidden() === true) return;
1586 if(that.chart_created === true) {
1587 if(NETDATA.options.current.destroy_on_hide === true) {
1588 // we should destroy it
1593 that.element_chart.style.display = 'none';
1594 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1595 that.tm.last_hidden = Date.now();
1598 // This works, but I not sure there are no corner cases somewhere
1599 // so it is commented - if the user has memory issues he can
1600 // set Destroy on Hide for all charts
1601 // that.data = null;
1605 that.___chartIsHidden___ = true;
1608 // unhide the chart, when it is visible - called from isVisible()
1609 var unhideChart = function() {
1610 if(isHidden() === false) return;
1612 that.___chartIsHidden___ = undefined;
1613 that.updates_since_last_unhide = 0;
1615 if(that.chart_created === false) {
1616 // we need to re-initialize it, to show our background
1617 // logo in bootstrap tabs, until the chart loads
1621 that.tm.last_unhidden = Date.now();
1622 that.element_chart.style.display = '';
1623 if(that.element_legend !== null) that.element_legend.style.display = '';
1629 var canBeRendered = function() {
1630 if(isHidden() === true || that.isVisible(true) === false)
1636 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1637 var callChartLibraryUpdateSafely = function(data) {
1640 if(canBeRendered() === false)
1643 if(NETDATA.options.debug.chart_errors === true)
1644 status = that.library.update(that, data);
1647 status = that.library.update(that, data);
1654 if(status === false) {
1655 error('chart failed to be updated as ' + that.library_name);
1662 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1663 var callChartLibraryCreateSafely = function(data) {
1666 if(canBeRendered() === false)
1669 if(NETDATA.options.debug.chart_errors === true)
1670 status = that.library.create(that, data);
1673 status = that.library.create(that, data);
1680 if(status === false) {
1681 error('chart failed to be created as ' + that.library_name);
1685 that.chart_created = true;
1686 that.updates_since_last_creation = 0;
1690 // ----------------------------------------------------------------------------------------------------------------
1693 // resizeChart() - private
1694 // to be called just before the chart library to make sure that
1695 // a properly sized dom is available
1696 var resizeChart = function() {
1697 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1698 if(that.chart_created === false) return;
1700 if(that.needsRecreation()) {
1703 else if(typeof that.library.resize === 'function') {
1704 that.library.resize(that);
1706 if(that.element_legend_childs.perfect_scroller !== null)
1707 Ps.update(that.element_legend_childs.perfect_scroller);
1709 NETDATA.noLayoutTrashing.add(maxMessageFontSize);
1712 that.tm.last_resized = Date.now();
1716 // this is the actual chart resize algorithm
1718 // - resize the entire container
1719 // - update the internal states
1720 // - resize the chart as the div changes height
1721 // - update the scrollbar of the legend
1722 var resizeChartToHeight = function(h) {
1724 that.element.style.height = h;
1726 if(that.settings_id !== null)
1727 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1729 var now = Date.now();
1730 NETDATA.options.last_page_scroll = now;
1731 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1734 that.tm.last_resized = 0;
1738 this.resizeHandler = function(e) {
1741 if(typeof this.event_resize === 'undefined'
1742 || this.event_resize.chart_original_w === 'undefined'
1743 || this.event_resize.chart_original_h === 'undefined')
1744 this.event_resize = {
1745 chart_original_w: this.element.clientWidth,
1746 chart_original_h: this.element.clientHeight,
1750 if(e.type === 'touchstart') {
1751 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1752 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1755 this.event_resize.mouse_start_x = e.clientX;
1756 this.event_resize.mouse_start_y = e.clientY;
1759 this.event_resize.chart_start_w = this.element.clientWidth;
1760 this.event_resize.chart_start_h = this.element.clientHeight;
1761 this.event_resize.chart_last_w = this.element.clientWidth;
1762 this.event_resize.chart_last_h = this.element.clientHeight;
1764 var now = Date.now();
1765 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller != null) {
1766 // double click / double tap event
1768 // console.dir(this.element_legend_childs.content);
1769 // console.dir(this.element_legend_childs.perfect_scroller);
1771 // the optimal height of the chart
1772 // showing the entire legend
1773 var optimal = this.event_resize.chart_last_h
1774 + this.element_legend_childs.perfect_scroller.scrollHeight
1775 - this.element_legend_childs.perfect_scroller.clientHeight;
1777 // if we are not optimal, be optimal
1778 if(this.event_resize.chart_last_h != optimal) {
1779 // 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());
1780 resizeChartToHeight(optimal.toString() + 'px');
1783 // else if the current height is not the original/saved height
1784 // reset to the original/saved height
1785 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h) {
1786 // 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());
1787 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1790 // else if the current height is not the internal default height
1791 // reset to the internal default height
1792 else if((this.event_resize.chart_last_h.toString() + 'px') != this.height_original) {
1793 // 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());
1794 resizeChartToHeight(this.height_original.toString());
1797 // else if the current height is not the firstchild's clientheight
1799 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1800 var parent_rect = this.element.getBoundingClientRect();
1801 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1802 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1804 // console.log(parent_rect);
1805 // console.log(content_rect);
1806 // console.log(wanted);
1808 // 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' );
1809 if(this.event_resize.chart_last_h != wanted)
1810 resizeChartToHeight(wanted.toString() + 'px');
1814 this.event_resize.last = now;
1816 // process movement event
1817 document.onmousemove =
1818 document.ontouchmove =
1819 this.element_legend_childs.resize_handler.onmousemove =
1820 this.element_legend_childs.resize_handler.ontouchmove =
1825 case 'mousemove': y = e.clientY; break;
1826 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1830 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1832 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1833 resizeChartToHeight(newH.toString() + 'px');
1834 that.event_resize.chart_last_h = newH;
1839 // process end event
1840 document.onmouseup =
1841 document.ontouchend =
1842 this.element_legend_childs.resize_handler.onmouseup =
1843 this.element_legend_childs.resize_handler.ontouchend =
1845 // remove all the hooks
1846 document.onmouseup =
1847 document.onmousemove =
1848 document.ontouchmove =
1849 document.ontouchend =
1850 that.element_legend_childs.resize_handler.onmousemove =
1851 that.element_legend_childs.resize_handler.ontouchmove =
1852 that.element_legend_childs.resize_handler.onmouseout =
1853 that.element_legend_childs.resize_handler.onmouseup =
1854 that.element_legend_childs.resize_handler.ontouchend =
1857 // allow auto-refreshes
1858 NETDATA.options.auto_refresher_stop_until = 0;
1864 var noDataToShow = function() {
1865 showMessageIcon('<i class="fa fa-warning"></i> empty');
1866 that.legendUpdateDOM();
1867 that.tm.last_autorefreshed = Date.now();
1868 // that.data_update_every = 30 * 1000;
1869 //that.element_chart.style.display = 'none';
1870 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1871 //that.___chartIsHidden___ = true;
1874 // ============================================================================================================
1877 this.error = function(msg) {
1881 this.setMode = function(m) {
1882 if(this.current !== null && this.current.name === m) return;
1885 this.current = this.auto;
1886 else if(m === 'pan')
1887 this.current = this.pan;
1888 else if(m === 'zoom')
1889 this.current = this.zoom;
1891 this.current = this.auto;
1893 this.current.force_update_at = 0;
1894 this.current.force_before_ms = null;
1895 this.current.force_after_ms = null;
1897 this.tm.last_mode_switch = Date.now();
1900 // ----------------------------------------------------------------------------------------------------------------
1901 // global selection sync
1903 // prevent to global selection sync for some time
1904 this.globalSelectionSyncDelay = function(ms) {
1905 if(NETDATA.options.current.sync_selection === false)
1908 if(typeof ms === 'number')
1909 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1911 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1914 // can we globally apply selection sync?
1915 this.globalSelectionSyncAbility = function() {
1916 if(NETDATA.options.current.sync_selection === false)
1919 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1925 this.globalSelectionSyncIsMaster = function() {
1926 if(NETDATA.globalSelectionSync.state === this)
1932 // this chart is the master of the global selection sync
1933 this.globalSelectionSyncBeMaster = function() {
1935 if(this.globalSelectionSyncIsMaster()) {
1936 if(this.debug === true)
1937 this.log('sync: I am the master already.');
1942 if(NETDATA.globalSelectionSync.state) {
1943 if(this.debug === true)
1944 this.log('sync: I am not the sync master. Resetting global sync.');
1946 this.globalSelectionSyncStop();
1949 // become the master
1950 if(this.debug === true)
1951 this.log('sync: becoming sync master.');
1953 this.selected = true;
1954 NETDATA.globalSelectionSync.state = this;
1956 // find the all slaves
1957 var targets = NETDATA.options.targets;
1958 var len = targets.length;
1963 if(this.debug === true)
1964 st.log('sync: not adding me to sync');
1966 else if(st.globalSelectionSyncIsEligible()) {
1967 if(this.debug === true)
1968 st.log('sync: adding to sync as slave');
1970 st.globalSelectionSyncBeSlave();
1974 // this.globalSelectionSyncDelay(100);
1977 // can the chart participate to the global selection sync as a slave?
1978 this.globalSelectionSyncIsEligible = function() {
1979 if(this.enabled === true
1980 && this.library !== null
1981 && typeof this.library.setSelection === 'function'
1982 && this.isVisible() === true
1983 && this.chart_created === true)
1989 // this chart becomes a slave of the global selection sync
1990 this.globalSelectionSyncBeSlave = function() {
1991 if(NETDATA.globalSelectionSync.state !== this)
1992 NETDATA.globalSelectionSync.slaves.push(this);
1995 // sync all the visible charts to the given time
1996 // this is to be called from the chart libraries
1997 this.globalSelectionSync = function(t) {
1998 if(this.globalSelectionSyncAbility() === false) {
1999 if(this.debug === true)
2000 this.log('sync: cannot sync (yet?).');
2005 if(this.globalSelectionSyncIsMaster() === false) {
2006 if(this.debug === true)
2007 this.log('sync: trying to be sync master.');
2009 this.globalSelectionSyncBeMaster();
2011 if(this.globalSelectionSyncAbility() === false) {
2012 if(this.debug === true)
2013 this.log('sync: cannot sync (yet?).');
2019 NETDATA.globalSelectionSync.last_t = t;
2020 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2025 // stop syncing all charts to the given time
2026 this.globalSelectionSyncStop = function() {
2027 if(NETDATA.globalSelectionSync.slaves.length) {
2028 if(this.debug === true)
2029 this.log('sync: cleaning up...');
2031 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2033 if(that.debug === true)
2034 st.log('sync: not adding me to sync stop');
2037 if(that.debug === true)
2038 st.log('sync: removed slave from sync');
2040 st.clearSelection();
2044 NETDATA.globalSelectionSync.last_t = 0;
2045 NETDATA.globalSelectionSync.slaves = [];
2046 NETDATA.globalSelectionSync.state = null;
2049 this.clearSelection();
2052 this.setSelection = function(t) {
2053 if(typeof this.library.setSelection === 'function') {
2054 if(this.library.setSelection(this, t) === true)
2055 this.selected = true;
2057 this.selected = false;
2059 else this.selected = true;
2061 if(this.selected === true && this.debug === true)
2062 this.log('selection set to ' + t.toString());
2064 return this.selected;
2067 this.clearSelection = function() {
2068 if(this.selected === true) {
2069 if(typeof this.library.clearSelection === 'function') {
2070 if(this.library.clearSelection(this) === true)
2071 this.selected = false;
2073 this.selected = true;
2075 else this.selected = false;
2077 if(this.selected === false && this.debug === true)
2078 this.log('selection cleared');
2083 return this.selected;
2086 // find if a timestamp (ms) is shown in the current chart
2087 this.timeIsVisible = function(t) {
2088 if(t >= this.data_after && t <= this.data_before)
2093 this.calculateRowForTime = function(t) {
2094 if(this.timeIsVisible(t) === false) return -1;
2095 return Math.floor((t - this.data_after) / this.data_update_every);
2098 // ----------------------------------------------------------------------------------------------------------------
2101 this.log = function(msg) {
2102 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2105 this.pauseChart = function() {
2106 if(this.paused === false) {
2107 if(this.debug === true)
2108 this.log('pauseChart()');
2114 this.unpauseChart = function() {
2115 if(this.paused === true) {
2116 if(this.debug === true)
2117 this.log('unpauseChart()');
2119 this.paused = false;
2123 this.resetChart = function(dont_clear_master, dont_update) {
2124 if(this.debug === true)
2125 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2127 if(typeof dont_clear_master === 'undefined')
2128 dont_clear_master = false;
2130 if(typeof dont_update === 'undefined')
2131 dont_update = false;
2133 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2134 if(this.debug === true)
2135 this.log('resetChart() diverting to clearMaster().');
2136 // this will call us back with master === true
2137 NETDATA.globalPanAndZoom.clearMaster();
2141 this.clearSelection();
2143 this.tm.pan_and_zoom_seq = 0;
2145 this.setMode('auto');
2146 this.current.force_update_at = 0;
2147 this.current.force_before_ms = null;
2148 this.current.force_after_ms = null;
2149 this.tm.last_autorefreshed = 0;
2150 this.paused = false;
2151 this.selected = false;
2152 this.enabled = true;
2153 // this.debug = false;
2155 // do not update the chart here
2156 // or the chart will flip-flop when it is the master
2157 // of a selection sync and another chart becomes
2160 if(dont_update !== true && this.isVisible() === true) {
2165 this.updateChartPanOrZoom = function(after, before) {
2166 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2169 if(this.debug === true)
2172 if(before < after) {
2173 if(this.debug === true)
2174 this.log(logme + 'flipped parameters, rejecting it.');
2179 if(typeof this.fixed_min_duration === 'undefined')
2180 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2182 var min_duration = this.fixed_min_duration;
2183 var current_duration = Math.round(this.view_before - this.view_after);
2185 // round the numbers
2186 after = Math.round(after);
2187 before = Math.round(before);
2189 // align them to update_every
2190 // stretching them further away
2191 after -= after % this.data_update_every;
2192 before += this.data_update_every - (before % this.data_update_every);
2194 // the final wanted duration
2195 var wanted_duration = before - after;
2197 // to allow panning, accept just a point below our minimum
2198 if((current_duration - this.data_update_every) < min_duration)
2199 min_duration = current_duration - this.data_update_every;
2201 // we do it, but we adjust to minimum size and return false
2202 // when the wanted size is below the current and the minimum
2204 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2205 if(this.debug === true)
2206 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2208 min_duration = this.fixed_min_duration;
2210 var dt = (min_duration - wanted_duration) / 2;
2213 wanted_duration = before - after;
2217 var tolerance = this.data_update_every * 2;
2218 var movement = Math.abs(before - this.view_before);
2220 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2221 if(this.debug === true)
2222 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);
2226 if(this.current.name === 'auto') {
2227 this.log(logme + 'caller called me with mode: ' + this.current.name);
2228 this.setMode('pan');
2231 if(this.debug === true)
2232 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);
2234 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2235 this.current.force_after_ms = after;
2236 this.current.force_before_ms = before;
2237 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2241 this.legendFormatValue = function(value) {
2242 if(value === null || value === 'undefined') return '-';
2243 if(typeof value !== 'number') return value;
2245 if(this.value_decimal_detail !== -1)
2246 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2248 var abs = Math.abs(value);
2249 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2250 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2251 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2252 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2253 return (Math.round(value * 10000) / 10000).toLocaleString();
2256 this.legendSetLabelValue = function(label, value) {
2257 var series = this.element_legend_childs.series[label];
2258 if(typeof series === 'undefined') return;
2259 if(series.value === null && series.user === null) return;
2262 // this slows down firefox and edge significantly
2263 // since it requires to use innerHTML(), instead of innerText()
2265 // if the value has not changed, skip DOM update
2266 //if(series.last === value) return;
2269 if(typeof value === 'number') {
2270 var v = Math.abs(value);
2271 s = r = this.legendFormatValue(value);
2273 if(typeof series.last === 'number') {
2274 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2275 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2276 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2278 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2288 series.last = value;
2292 var s = this.legendFormatValue(value);
2294 // caching: do not update the update to show the same value again
2295 if(s === series.last_shown_value) return;
2296 series.last_shown_value = s;
2298 if(series.value !== null) series.value.innerText = s;
2299 if(series.user !== null) series.user.innerText = s;
2302 this.__legendSetDateString = function(date) {
2303 if(date !== this.__last_shown_legend_date) {
2304 this.element_legend_childs.title_date.innerText = date;
2305 this.__last_shown_legend_date = date;
2309 this.__legendSetTimeString = function(time) {
2310 if(time !== this.__last_shown_legend_time) {
2311 this.element_legend_childs.title_time.innerText = time;
2312 this.__last_shown_legend_time = time;
2316 this.__legendSetUnitsString = function(units) {
2317 if(units !== this.__last_shown_legend_units) {
2318 this.element_legend_childs.title_units.innerText = units;
2319 this.__last_shown_legend_units = units;
2323 this.legendSetDate = function(ms) {
2324 if(typeof ms !== 'number') {
2325 this.legendShowUndefined();
2329 var d = new Date(ms);
2331 if(this.element_legend_childs.title_date)
2332 this.__legendSetDateString(d.toLocaleDateString());
2334 if(this.element_legend_childs.title_time)
2335 this.__legendSetTimeString(d.toLocaleTimeString());
2337 if(this.element_legend_childs.title_units)
2338 this.__legendSetUnitsString(this.units)
2341 this.legendShowUndefined = function() {
2342 if(this.element_legend_childs.title_date)
2343 this.__legendSetDateString(' ');
2345 if(this.element_legend_childs.title_time)
2346 this.__legendSetTimeString(this.chart.name);
2348 if(this.element_legend_childs.title_units)
2349 this.__legendSetUnitsString(' ')
2351 if(this.data && this.element_legend_childs.series !== null) {
2352 var labels = this.data.dimension_names;
2353 var i = labels.length;
2355 var label = labels[i];
2357 if(typeof label === 'undefined') continue;
2358 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2359 this.legendSetLabelValue(label, null);
2364 this.legendShowLatestValues = function() {
2365 if(this.chart === null) return;
2366 if(this.selected) return;
2368 if(this.data === null || this.element_legend_childs.series === null) {
2369 this.legendShowUndefined();
2373 var show_undefined = true;
2374 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2375 show_undefined = false;
2377 if(show_undefined) {
2378 this.legendShowUndefined();
2382 this.legendSetDate(this.view_before);
2384 var labels = this.data.dimension_names;
2385 var i = labels.length;
2387 var label = labels[i];
2389 if(typeof label === 'undefined') continue;
2390 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2393 this.legendSetLabelValue(label, null);
2395 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2399 this.legendReset = function() {
2400 this.legendShowLatestValues();
2403 // this should be called just ONCE per dimension per chart
2404 this._chartDimensionColor = function(label) {
2405 if(this.colors === null) this.chartColors();
2407 if(typeof this.colors_assigned[label] === 'undefined') {
2408 if(this.colors_available.length === 0) {
2409 var len = NETDATA.themes.current.colors.length;
2411 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2414 this.colors_assigned[label] = this.colors_available.shift();
2416 if(this.debug === true)
2417 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2420 if(this.debug === true)
2421 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2424 this.colors.push(this.colors_assigned[label]);
2425 return this.colors_assigned[label];
2428 this.chartColors = function() {
2429 if(this.colors !== null) return this.colors;
2431 this.colors = new Array();
2432 this.colors_available = new Array();
2434 // add the standard colors
2435 var len = NETDATA.themes.current.colors.length;
2437 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2439 // add the user supplied colors
2440 var c = $(this.element).data('colors');
2441 // this.log('read colors: ' + c);
2442 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2443 if(typeof c !== 'string') {
2444 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2454 this.colors_available.unshift(c[len]);
2455 // this.log('adding color: ' + c[len]);
2464 this.legendUpdateDOM = function() {
2467 // check that the legend DOM is up to date for the downloaded dimensions
2468 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2469 // this.log('the legend does not have any series - requesting legend update');
2472 else if(this.data === null) {
2473 // this.log('the chart does not have any data - requesting legend update');
2476 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2480 var labels = this.data.dimension_names.toString();
2481 if(labels !== this.element_legend_childs.series.labels_key) {
2484 if(this.debug === true)
2485 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2489 if(needed === false) {
2490 // make sure colors available
2493 // do we have to update the current values?
2494 // we do this, only when the visible chart is current
2495 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2496 if(this.debug === true)
2497 this.log('chart is in latest position... updating values on legend...');
2499 //var labels = this.data.dimension_names;
2500 //var i = labels.length;
2502 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2506 if(this.colors === null) {
2507 // this is the first time we update the chart
2508 // let's assign colors to all dimensions
2509 if(this.library.track_colors() === true)
2510 for(var dim in this.chart.dimensions)
2511 this._chartDimensionColor(this.chart.dimensions[dim].name);
2513 // we will re-generate the colors for the chart
2514 // based on the selected dimensions
2517 if(this.debug === true)
2518 this.log('updating Legend DOM');
2520 // mark all dimensions as invalid
2521 this.dimensions_visibility.invalidateAll();
2523 var genLabel = function(state, parent, dim, name, count) {
2524 var color = state._chartDimensionColor(name);
2526 var user_element = null;
2527 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2528 if(user_id === null)
2529 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2530 if(user_id !== null) {
2531 user_element = document.getElementById(user_id) || null;
2532 if (user_element === null)
2533 state.log('Cannot find element with id: ' + user_id);
2536 state.element_legend_childs.series[name] = {
2537 name: document.createElement('span'),
2538 value: document.createElement('span'),
2541 last_shown_value: null
2544 var label = state.element_legend_childs.series[name];
2546 // create the dimension visibility tracking for this label
2547 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2549 var rgb = NETDATA.colorHex2Rgb(color);
2550 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2551 + state.chart.chart_type
2552 + '" style="background-color: '
2553 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2554 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2556 var text = document.createTextNode(' ' + name);
2557 label.name.appendChild(text);
2560 parent.appendChild(document.createElement('br'));
2562 parent.appendChild(label.name);
2563 parent.appendChild(label.value);
2566 var content = document.createElement('div');
2568 if(this.hasLegend()) {
2569 this.element_legend_childs = {
2571 resize_handler: document.createElement('div'),
2572 toolbox: document.createElement('div'),
2573 toolbox_left: document.createElement('div'),
2574 toolbox_right: document.createElement('div'),
2575 toolbox_reset: document.createElement('div'),
2576 toolbox_zoomin: document.createElement('div'),
2577 toolbox_zoomout: document.createElement('div'),
2578 toolbox_volume: document.createElement('div'),
2579 title_date: document.createElement('span'),
2580 title_time: document.createElement('span'),
2581 title_units: document.createElement('span'),
2582 perfect_scroller: document.createElement('div'),
2586 this.element_legend.innerHTML = '';
2588 if(this.library.toolboxPanAndZoom !== null) {
2590 function get_pan_and_zoom_step(event) {
2592 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2594 else if (event.shiftKey)
2595 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2597 else if (event.altKey)
2598 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2601 return NETDATA.options.current.pan_and_zoom_factor;
2604 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2605 this.element.appendChild(this.element_legend_childs.toolbox);
2607 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2608 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2609 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2610 this.element_legend_childs.toolbox_left.onclick = function(e) {
2613 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2614 var before = that.view_before - step;
2615 var after = that.view_after - step;
2616 if(after >= that.netdata_first)
2617 that.library.toolboxPanAndZoom(that, after, before);
2619 if(NETDATA.options.current.show_help === true)
2620 $(this.element_legend_childs.toolbox_left).popover({
2625 placement: 'bottom',
2626 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2628 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>'
2632 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2633 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2634 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2635 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2637 NETDATA.resetAllCharts(that);
2639 if(NETDATA.options.current.show_help === true)
2640 $(this.element_legend_childs.toolbox_reset).popover({
2645 placement: 'bottom',
2646 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2647 title: 'Chart Reset',
2648 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>'
2651 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2652 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2653 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2654 this.element_legend_childs.toolbox_right.onclick = function(e) {
2656 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2657 var before = that.view_before + step;
2658 var after = that.view_after + step;
2659 if(before <= that.netdata_last)
2660 that.library.toolboxPanAndZoom(that, after, before);
2662 if(NETDATA.options.current.show_help === true)
2663 $(this.element_legend_childs.toolbox_right).popover({
2668 placement: 'bottom',
2669 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2671 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>'
2675 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2676 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2677 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2678 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2680 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2681 var before = that.view_before - dt;
2682 var after = that.view_after + dt;
2683 that.library.toolboxPanAndZoom(that, after, before);
2685 if(NETDATA.options.current.show_help === true)
2686 $(this.element_legend_childs.toolbox_zoomin).popover({
2691 placement: 'bottom',
2692 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2693 title: 'Chart Zoom In',
2694 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>'
2697 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2698 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2699 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2700 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2702 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);
2703 var before = that.view_before + dt;
2704 var after = that.view_after - dt;
2706 that.library.toolboxPanAndZoom(that, after, before);
2708 if(NETDATA.options.current.show_help === true)
2709 $(this.element_legend_childs.toolbox_zoomout).popover({
2714 placement: 'bottom',
2715 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2716 title: 'Chart Zoom Out',
2717 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>'
2720 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2721 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2722 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2723 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2724 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2725 //e.preventDefault();
2726 //alert('clicked toolbox_volume on ' + that.id);
2730 this.element_legend_childs.toolbox = null;
2731 this.element_legend_childs.toolbox_left = null;
2732 this.element_legend_childs.toolbox_reset = null;
2733 this.element_legend_childs.toolbox_right = null;
2734 this.element_legend_childs.toolbox_zoomin = null;
2735 this.element_legend_childs.toolbox_zoomout = null;
2736 this.element_legend_childs.toolbox_volume = null;
2739 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2740 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2741 this.element.appendChild(this.element_legend_childs.resize_handler);
2742 if(NETDATA.options.current.show_help === true)
2743 $(this.element_legend_childs.resize_handler).popover({
2748 placement: 'bottom',
2749 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2750 title: 'Chart Resize',
2751 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>'
2755 this.element_legend_childs.resize_handler.onmousedown =
2757 that.resizeHandler(e);
2761 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2762 that.resizeHandler(e);
2765 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2766 this.element_legend.appendChild(this.element_legend_childs.title_date);
2768 this.element_legend.appendChild(document.createElement('br'));
2770 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2771 this.element_legend.appendChild(this.element_legend_childs.title_time);
2773 this.element_legend.appendChild(document.createElement('br'));
2775 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2776 this.element_legend.appendChild(this.element_legend_childs.title_units);
2778 this.element_legend.appendChild(document.createElement('br'));
2780 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2781 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2783 content.className = 'netdata-legend-series-content';
2784 this.element_legend_childs.perfect_scroller.appendChild(content);
2786 if(NETDATA.options.current.show_help === true)
2787 $(content).popover({
2792 placement: 'bottom',
2793 title: 'Chart Legend',
2794 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2795 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>'
2799 this.element_legend_childs = {
2801 resize_handler: null,
2804 toolbox_right: null,
2805 toolbox_reset: null,
2806 toolbox_zoomin: null,
2807 toolbox_zoomout: null,
2808 toolbox_volume: null,
2812 perfect_scroller: null,
2818 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2819 if(this.debug === true)
2820 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2822 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2823 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2827 var tmp = new Array();
2828 for(var dim in this.chart.dimensions) {
2829 tmp.push(this.chart.dimensions[dim].name);
2830 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2832 this.element_legend_childs.series.labels_key = tmp.toString();
2833 if(this.debug === true)
2834 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2837 // create a hidden div to be used for hidding
2838 // the original legend of the chart library
2839 var el = document.createElement('div');
2840 if(this.element_legend !== null)
2841 this.element_legend.appendChild(el);
2842 el.style.display = 'none';
2844 this.element_legend_childs.hidden = document.createElement('div');
2845 el.appendChild(this.element_legend_childs.hidden);
2847 if(this.element_legend_childs.perfect_scroller !== null) {
2848 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2850 wheelPropagation: true,
2851 swipePropagation: true,
2852 minScrollbarLength: null,
2853 maxScrollbarLength: null,
2854 useBothWheelAxes: false,
2855 suppressScrollX: true,
2856 suppressScrollY: false,
2857 scrollXMarginOffset: 0,
2858 scrollYMarginOffset: 0,
2861 Ps.update(this.element_legend_childs.perfect_scroller);
2864 this.legendShowLatestValues();
2867 this.hasLegend = function() {
2868 if(typeof this.___hasLegendCache___ !== 'undefined')
2869 return this.___hasLegendCache___;
2872 if(this.library && this.library.legend(this) === 'right-side') {
2873 var legend = $(this.element).data('legend') || 'yes';
2874 if(legend === 'yes') leg = true;
2877 this.___hasLegendCache___ = leg;
2881 this.legendWidth = function() {
2882 return (this.hasLegend())?140:0;
2885 this.legendHeight = function() {
2886 return $(this.element).height();
2889 this.chartWidth = function() {
2890 return $(this.element).width() - this.legendWidth();
2893 this.chartHeight = function() {
2894 return $(this.element).height();
2897 this.chartPixelsPerPoint = function() {
2898 // force an options provided detail
2899 var px = this.pixels_per_point;
2901 if(this.library && px < this.library.pixels_per_point(this))
2902 px = this.library.pixels_per_point(this);
2904 if(px < NETDATA.options.current.pixels_per_point)
2905 px = NETDATA.options.current.pixels_per_point;
2910 this.needsRecreation = function() {
2912 this.chart_created === true
2914 && this.library.autoresize() === false
2915 && this.tm.last_resized < NETDATA.options.last_resized
2919 this.chartURL = function() {
2920 var after, before, points_multiplier = 1;
2921 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2922 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2924 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2925 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2926 this.view_after = after * 1000;
2927 this.view_before = before * 1000;
2929 this.requested_padding = null;
2930 points_multiplier = 1;
2932 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2933 this.tm.pan_and_zoom_seq = 0;
2935 before = Math.round(this.current.force_before_ms / 1000);
2936 after = Math.round(this.current.force_after_ms / 1000);
2937 this.view_after = after * 1000;
2938 this.view_before = before * 1000;
2940 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2941 this.requested_padding = Math.round((before - after) / 2);
2942 after -= this.requested_padding;
2943 before += this.requested_padding;
2944 this.requested_padding *= 1000;
2945 points_multiplier = 2;
2948 this.current.force_before_ms = null;
2949 this.current.force_after_ms = null;
2952 this.tm.pan_and_zoom_seq = 0;
2954 before = this.before;
2956 this.view_after = after * 1000;
2957 this.view_before = before * 1000;
2959 this.requested_padding = null;
2960 points_multiplier = 1;
2963 this.requested_after = after * 1000;
2964 this.requested_before = before * 1000;
2966 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2968 // build the data URL
2969 this.data_url = this.host + this.chart.data_url;
2970 this.data_url += "&format=" + this.library.format();
2971 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2972 this.data_url += "&group=" + this.method;
2974 if(this.override_options !== null)
2975 this.data_url += "&options=" + this.override_options.toString();
2977 this.data_url += "&options=" + this.library.options(this);
2979 this.data_url += '|jsonwrap';
2981 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2982 this.data_url += '|nonzero';
2984 if(this.append_options !== null)
2985 this.data_url += '|' + this.append_options.toString();
2988 this.data_url += "&after=" + after.toString();
2991 this.data_url += "&before=" + before.toString();
2994 this.data_url += "&dimensions=" + this.dimensions;
2996 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2997 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3000 this.redrawChart = function() {
3001 if(this.data !== null)
3002 this.updateChartWithData(this.data);
3005 this.updateChartWithData = function(data) {
3006 if(this.debug === true)
3007 this.log('updateChartWithData() called.');
3009 // this may force the chart to be re-created
3013 this.updates_counter++;
3014 this.updates_since_last_unhide++;
3015 this.updates_since_last_creation++;
3017 var started = Date.now();
3019 // if the result is JSON, find the latest update-every
3020 this.data_update_every = data.view_update_every * 1000;
3021 this.data_after = data.after * 1000;
3022 this.data_before = data.before * 1000;
3023 this.netdata_first = data.first_entry * 1000;
3024 this.netdata_last = data.last_entry * 1000;
3025 this.data_points = data.points;
3028 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3029 if(this.view_after < this.data_after) {
3030 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3031 this.view_after = this.data_after;
3034 if(this.view_before > this.data_before) {
3035 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3036 this.view_before = this.data_before;
3040 this.view_after = this.data_after;
3041 this.view_before = this.data_before;
3044 if(this.debug === true) {
3045 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3047 if(this.current.force_after_ms)
3048 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3050 this.log('STATUS: forced : unset');
3052 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3053 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3054 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3055 this.log('STATUS: points : ' + (this.data_points).toString());
3058 if(this.data_points === 0) {
3063 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3064 if(this.debug === true)
3065 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3067 this.chart_created = false;
3070 // check and update the legend
3071 this.legendUpdateDOM();
3073 if(this.chart_created === true
3074 && typeof this.library.update === 'function') {
3076 if(this.debug === true)
3077 this.log('updating chart...');
3079 if(callChartLibraryUpdateSafely(data) === false)
3083 if(this.debug === true)
3084 this.log('creating chart...');
3086 if(callChartLibraryCreateSafely(data) === false)
3090 this.legendShowLatestValues();
3091 if(this.selected === true)
3092 NETDATA.globalSelectionSync.stop();
3094 // update the performance counters
3095 var now = Date.now();
3096 this.tm.last_updated = now;
3098 // don't update last_autorefreshed if this chart is
3099 // forced to be updated with global PanAndZoom
3100 if(NETDATA.globalPanAndZoom.isActive())
3101 this.tm.last_autorefreshed = 0;
3103 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3104 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3106 this.tm.last_autorefreshed = now;
3109 this.refresh_dt_ms = now - started;
3110 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3112 if(this.refresh_dt_element !== null)
3113 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3116 this.updateChart = function(callback) {
3117 if(this.debug === true)
3118 this.log('updateChart() called.');
3120 if(this._updating === true) {
3121 if(this.debug === true)
3122 this.log('I am already updating...');
3124 if(typeof callback === 'function') callback();
3128 // due to late initialization of charts and libraries
3129 // we need to check this too
3130 if(this.enabled === false) {
3131 if(this.debug === true)
3132 this.log('I am not enabled');
3134 if(typeof callback === 'function') callback();
3138 if(canBeRendered() === false) {
3139 if(typeof callback === 'function') callback();
3143 if(this.chart === null) {
3144 this.getChart(function() { that.updateChart(callback); });
3148 if(this.library.initialized === false) {
3149 if(this.library.enabled === true) {
3150 this.library.initialize(function() { that.updateChart(callback); });
3154 error('chart library "' + this.library_name + '" is not available.');
3155 if(typeof callback === 'function') callback();
3160 this.clearSelection();
3163 if(this.debug === true)
3164 this.log('updating from ' + this.data_url);
3166 NETDATA.statistics.refreshes_total++;
3167 NETDATA.statistics.refreshes_active++;
3169 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3170 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3172 this._updating = true;
3174 this.xhr = $.ajax( {
3179 'Cache-Control': 'no-cache, no-store',
3180 'Pragma': 'no-cache'
3182 xhrFields: { withCredentials: true } // required for the cookie
3184 .done(function(data) {
3185 that.xhr = undefined;
3186 that.retries_on_data_failures = 0;
3188 if(that.debug === true)
3189 that.log('data received. updating chart.');
3191 that.updateChartWithData(data);
3193 .fail(function(msg) {
3194 that.xhr = undefined;
3196 if(msg.statusText !== 'abort') {
3197 that.retries_on_data_failures++;
3198 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3199 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3200 that.retries_on_data_failures = 0;
3201 error('data download failed for url: ' + that.data_url);
3204 that.tm.last_autorefreshed = Date.now();
3205 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3209 .always(function() {
3210 that.xhr = undefined;
3212 NETDATA.statistics.refreshes_active--;
3213 that._updating = false;
3214 if(typeof callback === 'function') callback();
3220 this.isVisible = function(nocache) {
3221 if(typeof nocache === 'undefined')
3224 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3226 // caching - we do not evaluate the charts visibility
3227 // if the page has not been scrolled since the last check
3228 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3229 return this.___isVisible___;
3231 this.tm.last_visible_check = Date.now();
3233 var wh = window.innerHeight;
3234 var x = this.element.getBoundingClientRect();
3238 if(x.width === 0 || x.height === 0) {
3240 this.___isVisible___ = false;
3241 return this.___isVisible___;
3244 if(x.top < 0 && -x.top > x.height) {
3245 // the chart is entirely above
3246 ret = -x.top - x.height;
3248 else if(x.top > wh) {
3249 // the chart is entirely below
3253 if(ret > tolerance) {
3254 // the chart is too far
3257 this.___isVisible___ = false;
3258 return this.___isVisible___;
3261 // the chart is inside or very close
3264 this.___isVisible___ = true;
3265 return this.___isVisible___;
3269 this.isAutoRefreshable = function() {
3270 return (this.current.autorefresh);
3273 this.canBeAutoRefreshed = function() {
3274 var now = Date.now();
3276 if(this.running === true) {
3277 if(this.debug === true)
3278 this.log('I am already running');
3283 if(this.enabled === false) {
3284 if(this.debug === true)
3285 this.log('I am not enabled');
3290 if(this.library === null || this.library.enabled === false) {
3291 error('charting library "' + this.library_name + '" is not available');
3292 if(this.debug === true)
3293 this.log('My chart library ' + this.library_name + ' is not available');
3298 if(this.isVisible() === false) {
3299 if(NETDATA.options.debug.visibility === true || this.debug === true)
3300 this.log('I am not visible');
3305 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3306 if(this.debug === true)
3307 this.log('timed force update detected - allowing this update');
3309 this.current.force_update_at = 0;
3313 if(this.isAutoRefreshable() === true) {
3314 // allow the first update, even if the page is not visible
3315 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3316 if(NETDATA.options.debug.focus === true || this.debug === true)
3317 this.log('canBeAutoRefreshed(): page does not have focus');
3322 if(this.needsRecreation() === true) {
3323 if(this.debug === true)
3324 this.log('canBeAutoRefreshed(): needs re-creation.');
3329 // options valid only for autoRefresh()
3330 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3331 if(NETDATA.globalPanAndZoom.isActive()) {
3332 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3333 if(this.debug === true)
3334 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3339 if(this.debug === true)
3340 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3346 if(this.selected === true) {
3347 if(this.debug === true)
3348 this.log('canBeAutoRefreshed(): I have a selection in place.');
3353 if(this.paused === true) {
3354 if(this.debug === true)
3355 this.log('canBeAutoRefreshed(): I am paused.');
3360 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3361 if(this.debug === true)
3362 this.log('canBeAutoRefreshed(): It is time to update me.');
3372 this.autoRefresh = function(callback) {
3373 if(this.canBeAutoRefreshed() === true && this.running === false) {
3376 state.running = true;
3377 state.updateChart(function() {
3378 state.running = false;
3380 if(typeof callback !== 'undefined')
3385 if(typeof callback !== 'undefined')
3390 this._defaultsFromDownloadedChart = function(chart) {
3392 this.chart_url = chart.url;
3393 this.data_update_every = chart.update_every * 1000;
3394 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3395 this.tm.last_info_downloaded = Date.now();
3397 if(this.title === null)
3398 this.title = chart.title;
3400 if(this.units === null)
3401 this.units = chart.units;
3404 // fetch the chart description from the netdata server
3405 this.getChart = function(callback) {
3406 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3408 this._defaultsFromDownloadedChart(this.chart);
3409 if(typeof callback === 'function') callback();
3412 this.chart_url = "/api/v1/chart?chart=" + this.id;
3414 if(this.debug === true)
3415 this.log('downloading ' + this.chart_url);
3418 url: this.host + this.chart_url,
3421 xhrFields: { withCredentials: true } // required for the cookie
3423 .done(function(chart) {
3424 chart.url = that.chart_url;
3425 that._defaultsFromDownloadedChart(chart);
3426 NETDATA.chartRegistry.add(that.host, that.id, chart);
3429 NETDATA.error(404, that.chart_url);
3430 error('chart not found on url "' + that.chart_url + '"');
3432 .always(function() {
3433 if(typeof callback === 'function') callback();
3438 // ============================================================================================================
3444 NETDATA.resetAllCharts = function(state) {
3445 // first clear the global selection sync
3446 // to make sure no chart is in selected state
3447 state.globalSelectionSyncStop();
3449 // there are 2 possibilities here
3450 // a. state is the global Pan and Zoom master
3451 // b. state is not the global Pan and Zoom master
3453 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3456 // clear the global Pan and Zoom
3457 // this will also refresh the master
3458 // and unblock any charts currently mirroring the master
3459 NETDATA.globalPanAndZoom.clearMaster();
3461 // if we were not the master, reset our status too
3462 // this is required because most probably the mouse
3463 // is over this chart, blocking it from auto-refreshing
3464 if(master === false && (state.paused === true || state.selected === true))
3468 // get or create a chart state, given a DOM element
3469 NETDATA.chartState = function(element) {
3470 var state = $(element).data('netdata-state-object') || null;
3471 if(state === null) {
3472 state = new chartState(element);
3473 $(element).data('netdata-state-object', state);
3478 // ----------------------------------------------------------------------------------------------------------------
3479 // Library functions
3481 // Load a script without jquery
3482 // This is used to load jquery - after it is loaded, we use jquery
3483 NETDATA._loadjQuery = function(callback) {
3484 if(typeof jQuery === 'undefined') {
3485 if(NETDATA.options.debug.main_loop === true)
3486 console.log('loading ' + NETDATA.jQuery);
3488 var script = document.createElement('script');
3489 script.type = 'text/javascript';
3490 script.async = true;
3491 script.src = NETDATA.jQuery;
3493 // script.onabort = onError;
3494 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3495 if(typeof callback === "function")
3496 script.onload = callback;
3498 var s = document.getElementsByTagName('script')[0];
3499 s.parentNode.insertBefore(script, s);
3501 else if(typeof callback === "function")
3505 NETDATA._loadCSS = function(filename) {
3506 // don't use jQuery here
3507 // styles are loaded before jQuery
3508 // to eliminate showing an unstyled page to the user
3510 var fileref = document.createElement("link");
3511 fileref.setAttribute("rel", "stylesheet");
3512 fileref.setAttribute("type", "text/css");
3513 fileref.setAttribute("href", filename);
3515 if (typeof fileref !== 'undefined')
3516 document.getElementsByTagName("head")[0].appendChild(fileref);
3519 NETDATA.colorHex2Rgb = function(hex) {
3520 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3521 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3522 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3523 return r + r + g + g + b + b;
3526 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3528 r: parseInt(result[1], 16),
3529 g: parseInt(result[2], 16),
3530 b: parseInt(result[3], 16)
3534 NETDATA.colorLuminance = function(hex, lum) {
3535 // validate hex string
3536 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3538 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3542 // convert to decimal and change luminosity
3543 var rgb = "#", c, i;
3544 for (i = 0; i < 3; i++) {
3545 c = parseInt(hex.substr(i*2,2), 16);
3546 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3547 rgb += ("00"+c).substr(c.length);
3553 NETDATA.guid = function() {
3555 return Math.floor((1 + Math.random()) * 0x10000)
3560 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3563 NETDATA.zeropad = function(x) {
3564 if(x > -10 && x < 10) return '0' + x.toString();
3565 else return x.toString();
3568 // user function to signal us the DOM has been
3570 NETDATA.updatedDom = function() {
3571 NETDATA.options.updated_dom = true;
3574 NETDATA.ready = function(callback) {
3575 NETDATA.options.pauseCallback = callback;
3578 NETDATA.pause = function(callback) {
3579 if(NETDATA.options.pause === true)
3582 NETDATA.options.pauseCallback = callback;
3585 NETDATA.unpause = function() {
3586 NETDATA.options.pauseCallback = null;
3587 NETDATA.options.updated_dom = true;
3588 NETDATA.options.pause = false;
3591 // ----------------------------------------------------------------------------------------------------------------
3593 // this is purely sequencial charts refresher
3594 // it is meant to be autonomous
3595 NETDATA.chartRefresherNoParallel = function(index) {
3596 if(NETDATA.options.debug.mail_loop === true)
3597 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3599 if(NETDATA.options.updated_dom === true) {
3600 // the dom has been updated
3601 // get the dom parts again
3602 NETDATA.parseDom(NETDATA.chartRefresher);
3605 if(index >= NETDATA.options.targets.length) {
3606 if(NETDATA.options.debug.main_loop === true)
3607 console.log('waiting to restart main loop...');
3609 NETDATA.options.auto_refresher_fast_weight = 0;
3611 setTimeout(function() {
3612 NETDATA.chartRefresher();
3613 }, NETDATA.options.current.idle_between_loops);
3616 var state = NETDATA.options.targets[index];
3618 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3619 if(NETDATA.options.debug.main_loop === true)
3620 console.log('fast rendering...');
3622 state.autoRefresh(function() {
3623 NETDATA.chartRefresherNoParallel(++index);
3627 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3628 NETDATA.options.auto_refresher_fast_weight = 0;
3630 setTimeout(function() {
3631 state.autoRefresh(function() {
3632 NETDATA.chartRefresherNoParallel(++index);
3634 }, NETDATA.options.current.idle_between_charts);
3639 // this is part of the parallel refresher
3640 // its cause is to refresh sequencially all the charts
3641 // that depend on chart library initialization
3642 // it will call the parallel refresher back
3643 // as soon as it sees a chart that its chart library
3645 NETDATA.chartRefresher_uninitialized = function() {
3646 if(NETDATA.options.updated_dom === true) {
3647 // the dom has been updated
3648 // get the dom parts again
3649 NETDATA.parseDom(NETDATA.chartRefresher);
3653 if(NETDATA.options.sequencial.length === 0)
3654 NETDATA.chartRefresher();
3656 var state = NETDATA.options.sequencial.pop();
3657 if(state.library.initialized === true)
3658 NETDATA.chartRefresher();
3660 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3664 NETDATA.chartRefresherWaitTime = function() {
3665 return NETDATA.options.current.idle_parallel_loops;
3668 // the default refresher
3669 // it will create 2 sets of charts:
3670 // - the ones that can be refreshed in parallel
3671 // - the ones that depend on something else
3672 // the first set will be executed in parallel
3673 // the second will be given to NETDATA.chartRefresher_uninitialized()
3674 NETDATA.chartRefresher = function() {
3675 // console.log('auto-refresher...');
3677 if(NETDATA.options.pause === true) {
3678 // console.log('auto-refresher is paused');
3679 setTimeout(NETDATA.chartRefresher,
3680 NETDATA.chartRefresherWaitTime());
3684 if(typeof NETDATA.options.pauseCallback === 'function') {
3685 // console.log('auto-refresher is calling pauseCallback');
3686 NETDATA.options.pause = true;
3687 NETDATA.options.pauseCallback();
3688 NETDATA.chartRefresher();
3692 if(NETDATA.options.current.parallel_refresher === false) {
3693 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3694 NETDATA.chartRefresherNoParallel(0);
3698 if(NETDATA.options.updated_dom === true) {
3699 // the dom has been updated
3700 // get the dom parts again
3701 // console.log('auto-refresher is calling parseDom()');
3702 NETDATA.parseDom(NETDATA.chartRefresher);
3706 var parallel = new Array();
3707 var targets = NETDATA.options.targets;
3708 var len = targets.length;
3711 state = targets[len];
3712 if(state.isVisible() === false || state.running === true)
3715 if(state.library.initialized === false) {
3716 if(state.library.enabled === true) {
3717 state.library.initialize(NETDATA.chartRefresher);
3721 state.error('chart library "' + state.library_name + '" is not enabled.');
3725 parallel.unshift(state);
3728 if(parallel.length > 0) {
3729 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3730 // this will execute the jobs in parallel
3731 $(parallel).each(function() {
3736 // console.log('auto-refresher nothing to do');
3739 // run the next refresh iteration
3740 setTimeout(NETDATA.chartRefresher,
3741 NETDATA.chartRefresherWaitTime());
3744 NETDATA.parseDom = function(callback) {
3745 NETDATA.options.last_page_scroll = Date.now();
3746 NETDATA.options.updated_dom = false;
3748 var targets = $('div[data-netdata]'); //.filter(':visible');
3750 if(NETDATA.options.debug.main_loop === true)
3751 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3753 NETDATA.options.targets = new Array();
3754 var len = targets.length;
3756 // the initialization will take care of sizing
3757 // and the "loading..." message
3758 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3761 if(typeof callback === 'function') callback();
3764 // this is the main function - where everything starts
3765 NETDATA.start = function() {
3766 // this should be called only once
3768 NETDATA.options.page_is_visible = true;
3770 $(window).blur(function() {
3771 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3772 NETDATA.options.page_is_visible = false;
3773 if(NETDATA.options.debug.focus === true)
3774 console.log('Lost Focus!');
3778 $(window).focus(function() {
3779 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3780 NETDATA.options.page_is_visible = true;
3781 if(NETDATA.options.debug.focus === true)
3782 console.log('Focus restored!');
3786 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3787 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3788 NETDATA.options.page_is_visible = false;
3789 if(NETDATA.options.debug.focus === true)
3790 console.log('Document has no focus!');
3794 // bootstrap tab switching
3795 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3797 // bootstrap modal switching
3798 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3799 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3801 // bootstrap collapse switching
3802 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3803 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3805 NETDATA.parseDom(NETDATA.chartRefresher);
3807 // Alarms initialization
3808 setTimeout(NETDATA.alarms.init, 1000);
3810 // Registry initialization
3811 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3813 if(typeof netdataCallback === 'function')
3817 // ----------------------------------------------------------------------------------------------------------------
3820 NETDATA.peityInitialize = function(callback) {
3821 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3823 url: NETDATA.peity_js,
3826 xhrFields: { withCredentials: true } // required for the cookie
3829 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3832 NETDATA.chartLibraries.peity.enabled = false;
3833 NETDATA.error(100, NETDATA.peity_js);
3835 .always(function() {
3836 if(typeof callback === "function")
3841 NETDATA.chartLibraries.peity.enabled = false;
3842 if(typeof callback === "function")
3847 NETDATA.peityChartUpdate = function(state, data) {
3848 state.peity_instance.innerHTML = data.result;
3850 if(state.peity_options.stroke !== state.chartColors()[0]) {
3851 state.peity_options.stroke = state.chartColors()[0];
3852 if(state.chart.chart_type === 'line')
3853 state.peity_options.fill = NETDATA.themes.current.background;
3855 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3858 $(state.peity_instance).peity('line', state.peity_options);
3862 NETDATA.peityChartCreate = function(state, data) {
3863 state.peity_instance = document.createElement('div');
3864 state.element_chart.appendChild(state.peity_instance);
3866 var self = $(state.element);
3867 state.peity_options = {
3868 stroke: NETDATA.themes.current.foreground,
3869 strokeWidth: self.data('peity-strokewidth') || 1,
3870 width: state.chartWidth(),
3871 height: state.chartHeight(),
3872 fill: NETDATA.themes.current.foreground
3875 NETDATA.peityChartUpdate(state, data);
3879 // ----------------------------------------------------------------------------------------------------------------
3882 NETDATA.sparklineInitialize = function(callback) {
3883 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3885 url: NETDATA.sparkline_js,
3888 xhrFields: { withCredentials: true } // required for the cookie
3891 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3894 NETDATA.chartLibraries.sparkline.enabled = false;
3895 NETDATA.error(100, NETDATA.sparkline_js);
3897 .always(function() {
3898 if(typeof callback === "function")
3903 NETDATA.chartLibraries.sparkline.enabled = false;
3904 if(typeof callback === "function")
3909 NETDATA.sparklineChartUpdate = function(state, data) {
3910 state.sparkline_options.width = state.chartWidth();
3911 state.sparkline_options.height = state.chartHeight();
3913 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3917 NETDATA.sparklineChartCreate = function(state, data) {
3918 var self = $(state.element);
3919 var type = self.data('sparkline-type') || 'line';
3920 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3921 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3922 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3923 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3924 var composite = self.data('sparkline-composite') || undefined;
3925 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3926 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3927 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3928 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3929 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3930 var spotColor = self.data('sparkline-spotcolor') || undefined;
3931 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3932 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3933 var spotRadius = self.data('sparkline-spotradius') || undefined;
3934 var valueSpots = self.data('sparkline-valuespots') || undefined;
3935 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3936 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3937 var lineWidth = self.data('sparkline-linewidth') || undefined;
3938 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3939 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3940 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3941 var xvalues = self.data('sparkline-xvalues') || undefined;
3942 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3943 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3944 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3945 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3946 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3947 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3948 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3949 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3950 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3951 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3952 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3953 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3954 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3955 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3956 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3957 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3958 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3959 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3960 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3961 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3962 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3963 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3965 if(spotColor === 'disable') spotColor='';
3966 if(minSpotColor === 'disable') minSpotColor='';
3967 if(maxSpotColor === 'disable') maxSpotColor='';
3969 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3971 state.sparkline_options = {
3973 lineColor: lineColor,
3974 fillColor: fillColor,
3975 chartRangeMin: chartRangeMin,
3976 chartRangeMax: chartRangeMax,
3977 composite: composite,
3978 enableTagOptions: enableTagOptions,
3979 tagOptionPrefix: tagOptionPrefix,
3980 tagValuesAttribute: tagValuesAttribute,
3981 disableHiddenCheck: disableHiddenCheck,
3982 defaultPixelsPerValue: defaultPixelsPerValue,
3983 spotColor: spotColor,
3984 minSpotColor: minSpotColor,
3985 maxSpotColor: maxSpotColor,
3986 spotRadius: spotRadius,
3987 valueSpots: valueSpots,
3988 highlightSpotColor: highlightSpotColor,
3989 highlightLineColor: highlightLineColor,
3990 lineWidth: lineWidth,
3991 normalRangeMin: normalRangeMin,
3992 normalRangeMax: normalRangeMax,
3993 drawNormalOnTop: drawNormalOnTop,
3995 chartRangeClip: chartRangeClip,
3996 chartRangeMinX: chartRangeMinX,
3997 chartRangeMaxX: chartRangeMaxX,
3998 disableInteraction: disableInteraction,
3999 disableTooltips: disableTooltips,
4000 disableHighlight: disableHighlight,
4001 highlightLighten: highlightLighten,
4002 highlightColor: highlightColor,
4003 tooltipContainer: tooltipContainer,
4004 tooltipClassname: tooltipClassname,
4005 tooltipChartTitle: state.title,
4006 tooltipFormat: tooltipFormat,
4007 tooltipPrefix: tooltipPrefix,
4008 tooltipSuffix: tooltipSuffix,
4009 tooltipSkipNull: tooltipSkipNull,
4010 tooltipValueLookups: tooltipValueLookups,
4011 tooltipFormatFieldlist: tooltipFormatFieldlist,
4012 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4013 numberFormatter: numberFormatter,
4014 numberDigitGroupSep: numberDigitGroupSep,
4015 numberDecimalMark: numberDecimalMark,
4016 numberDigitGroupCount: numberDigitGroupCount,
4017 animatedZooms: animatedZooms,
4018 width: state.chartWidth(),
4019 height: state.chartHeight()
4022 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4026 // ----------------------------------------------------------------------------------------------------------------
4033 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4034 if(after < state.netdata_first)
4035 after = state.netdata_first;
4037 if(before > state.netdata_last)
4038 before = state.netdata_last;
4040 state.setMode('zoom');
4041 state.globalSelectionSyncStop();
4042 state.globalSelectionSyncDelay();
4043 state.dygraph_user_action = true;
4044 state.dygraph_force_zoom = true;
4045 state.updateChartPanOrZoom(after, before);
4046 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4049 NETDATA.dygraphSetSelection = function(state, t) {
4050 if(typeof state.dygraph_instance !== 'undefined') {
4051 var r = state.calculateRowForTime(t);
4053 state.dygraph_instance.setSelection(r);
4055 state.dygraph_instance.clearSelection();
4056 state.legendShowUndefined();
4063 NETDATA.dygraphClearSelection = function(state, t) {
4064 if(typeof state.dygraph_instance !== 'undefined') {
4065 state.dygraph_instance.clearSelection();
4070 NETDATA.dygraphSmoothInitialize = function(callback) {
4072 url: NETDATA.dygraph_smooth_js,
4075 xhrFields: { withCredentials: true } // required for the cookie
4078 NETDATA.dygraph.smooth = true;
4079 smoothPlotter.smoothing = 0.3;
4082 NETDATA.dygraph.smooth = false;
4084 .always(function() {
4085 if(typeof callback === "function")
4090 NETDATA.dygraphInitialize = function(callback) {
4091 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4093 url: NETDATA.dygraph_js,
4096 xhrFields: { withCredentials: true } // required for the cookie
4099 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4102 NETDATA.chartLibraries.dygraph.enabled = false;
4103 NETDATA.error(100, NETDATA.dygraph_js);
4105 .always(function() {
4106 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4107 NETDATA.dygraphSmoothInitialize(callback);
4108 else if(typeof callback === "function")
4113 NETDATA.chartLibraries.dygraph.enabled = false;
4114 if(typeof callback === "function")
4119 NETDATA.dygraphChartUpdate = function(state, data) {
4120 var dygraph = state.dygraph_instance;
4122 if(typeof dygraph === 'undefined')
4123 return NETDATA.dygraphChartCreate(state, data);
4125 // when the chart is not visible, and hidden
4126 // if there is a window resize, dygraph detects
4127 // its element size as 0x0.
4128 // this will make it re-appear properly
4130 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4134 file: data.result.data,
4135 colors: state.chartColors(),
4136 labels: data.result.labels,
4137 labelsDivWidth: state.chartWidth() - 70,
4138 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4141 if(state.dygraph_force_zoom === true) {
4142 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4143 state.log('dygraphChartUpdate() forced zoom update');
4145 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4146 options.isZoomedIgnoreProgrammaticZoom = true;
4147 state.dygraph_force_zoom = false;
4149 else if(state.current.name !== 'auto') {
4150 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4151 state.log('dygraphChartUpdate() loose update');
4154 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4155 state.log('dygraphChartUpdate() strict update');
4157 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4158 options.isZoomedIgnoreProgrammaticZoom = true;
4161 options.valueRange = state.dygraph_options.valueRange;
4163 var oldMax = null, oldMin = null;
4164 if(state.__commonMin !== null) {
4165 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4166 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4168 if(state.__commonMax !== null) {
4169 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4170 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4173 if(state.dygraph_smooth_eligible === true) {
4174 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4175 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4176 NETDATA.dygraphChartCreate(state, data);
4181 dygraph.updateOptions(options);
4184 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4185 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4186 options.valueRange[0] = NETDATA.commonMin.get(state);
4189 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4190 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4191 options.valueRange[1] = NETDATA.commonMax.get(state);
4195 if(redraw === true) {
4196 // state.log('forcing redraw to adapt to common- min/max');
4197 dygraph.updateOptions(options);
4200 state.dygraph_last_rendered = Date.now();
4204 NETDATA.dygraphChartCreate = function(state, data) {
4205 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4206 state.log('dygraphChartCreate()');
4208 var self = $(state.element);
4210 var chart_type = state.chart.chart_type;
4211 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4212 chart_type = self.data('dygraph-type') || chart_type;
4214 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4215 smooth = self.data('dygraph-smooth') || smooth;
4217 if(NETDATA.dygraph.smooth === false)
4220 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4221 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4223 state.dygraph_options = {
4224 colors: self.data('dygraph-colors') || state.chartColors(),
4226 // leave a few pixels empty on the right of the chart
4227 rightGap: self.data('dygraph-rightgap') || 5,
4228 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4229 showRoller: self.data('dygraph-showroller') || false,
4231 title: self.data('dygraph-title') || state.title,
4232 titleHeight: self.data('dygraph-titleheight') || 19,
4234 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4235 labels: data.result.labels,
4236 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4237 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4238 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4239 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4240 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4243 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4244 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4246 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4247 xRangePad: self.data('dygraph-xrangepad') || 0,
4248 yRangePad: self.data('dygraph-yrangepad') || 1,
4250 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4252 ylabel: state.units,
4253 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4255 // the function to plot the chart
4258 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4259 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4260 strokePattern: self.data('dygraph-strokepattern') || undefined,
4262 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4263 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4264 drawPoints: self.data('dygraph-drawpoints') || false,
4266 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4267 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4269 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4270 pointSize: self.data('dygraph-pointsize') || 1,
4272 // enabling this makes the chart with little square lines
4273 stepPlot: self.data('dygraph-stepplot') || false,
4275 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4276 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4277 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4279 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4280 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4281 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4282 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4284 drawAxis: self.data('dygraph-drawaxis') || true,
4285 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4286 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4287 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4289 drawGrid: self.data('dygraph-drawgrid') || true,
4290 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4291 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4292 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4294 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4295 sigFigs: self.data('dygraph-sigfigs') || null,
4296 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4297 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4299 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4300 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4301 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4303 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4304 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4308 ticker: Dygraph.dateTicker,
4309 axisLabelFormatter: function (d, gran) {
4310 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4312 valueFormatter: function (ms) {
4313 //var d = new Date(ms);
4314 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4316 // no need to return anything here
4323 valueFormatter: function (x) {
4324 // we format legends with the state object
4325 // no need to do anything here
4326 // return (Math.round(x*100) / 100).toLocaleString();
4327 // return state.legendFormatValue(x);
4332 legendFormatter: function(data) {
4333 var elements = state.element_legend_childs;
4335 // if the hidden div is not there
4336 // we are not managing the legend
4337 if(elements.hidden === null) return;
4339 if (typeof data.x !== 'undefined') {
4340 state.legendSetDate(data.x);
4341 var i = data.series.length;
4343 var series = data.series[i];
4344 if(series.isVisible === true)
4345 state.legendSetLabelValue(series.label, series.y);
4351 drawCallback: function(dygraph, is_initial) {
4352 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4353 state.dygraph_user_action = false;
4355 var x_range = dygraph.xAxisRange();
4356 var after = Math.round(x_range[0]);
4357 var before = Math.round(x_range[1]);
4359 if(NETDATA.options.debug.dygraph === true)
4360 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4362 if(before <= state.netdata_last && after >= state.netdata_first)
4363 state.updateChartPanOrZoom(after, before);
4366 zoomCallback: function(minDate, maxDate, yRanges) {
4367 if(NETDATA.options.debug.dygraph === true)
4368 state.log('dygraphZoomCallback()');
4370 state.globalSelectionSyncStop();
4371 state.globalSelectionSyncDelay();
4372 state.setMode('zoom');
4374 // refresh it to the greatest possible zoom level
4375 state.dygraph_user_action = true;
4376 state.dygraph_force_zoom = true;
4377 state.updateChartPanOrZoom(minDate, maxDate);
4379 highlightCallback: function(event, x, points, row, seriesName) {
4380 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4381 state.log('dygraphHighlightCallback()');
4385 // there is a bug in dygraph when the chart is zoomed enough
4386 // the time it thinks is selected is wrong
4387 // here we calculate the time t based on the row number selected
4389 var t = state.data_after + row * state.data_update_every;
4390 // 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);
4392 state.globalSelectionSync(x);
4394 // fix legend zIndex using the internal structures of dygraph legend module
4395 // this works, but it is a hack!
4396 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4398 unhighlightCallback: function(event) {
4399 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4400 state.log('dygraphUnhighlightCallback()');
4402 state.unpauseChart();
4403 state.globalSelectionSyncStop();
4405 interactionModel : {
4406 mousedown: function(event, dygraph, context) {
4407 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4408 state.log('interactionModel.mousedown()');
4410 state.dygraph_user_action = true;
4411 state.globalSelectionSyncStop();
4413 if(NETDATA.options.debug.dygraph === true)
4414 state.log('dygraphMouseDown()');
4416 // Right-click should not initiate a zoom.
4417 if(event.button && event.button === 2) return;
4419 context.initializeMouseDown(event, dygraph, context);
4421 if(event.button && event.button === 1) {
4422 if (event.altKey || event.shiftKey) {
4423 state.setMode('pan');
4424 state.globalSelectionSyncDelay();
4425 Dygraph.startPan(event, dygraph, context);
4428 state.setMode('zoom');
4429 state.globalSelectionSyncDelay();
4430 Dygraph.startZoom(event, dygraph, context);
4434 if (event.altKey || event.shiftKey) {
4435 state.setMode('zoom');
4436 state.globalSelectionSyncDelay();
4437 Dygraph.startZoom(event, dygraph, context);
4440 state.setMode('pan');
4441 state.globalSelectionSyncDelay();
4442 Dygraph.startPan(event, dygraph, context);
4446 mousemove: function(event, dygraph, context) {
4447 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4448 state.log('interactionModel.mousemove()');
4450 if(context.isPanning) {
4451 state.dygraph_user_action = true;
4452 state.globalSelectionSyncStop();
4453 state.globalSelectionSyncDelay();
4454 state.setMode('pan');
4455 context.is2DPan = false;
4456 Dygraph.movePan(event, dygraph, context);
4458 else if(context.isZooming) {
4459 state.dygraph_user_action = true;
4460 state.globalSelectionSyncStop();
4461 state.globalSelectionSyncDelay();
4462 state.setMode('zoom');
4463 Dygraph.moveZoom(event, dygraph, context);
4466 mouseup: function(event, dygraph, context) {
4467 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4468 state.log('interactionModel.mouseup()');
4470 if (context.isPanning) {
4471 state.dygraph_user_action = true;
4472 state.globalSelectionSyncDelay();
4473 Dygraph.endPan(event, dygraph, context);
4475 else if (context.isZooming) {
4476 state.dygraph_user_action = true;
4477 state.globalSelectionSyncDelay();
4478 Dygraph.endZoom(event, dygraph, context);
4481 click: function(event, dygraph, context) {
4482 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4483 state.log('interactionModel.click()');
4485 event.preventDefault();
4487 dblclick: function(event, dygraph, context) {
4488 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4489 state.log('interactionModel.dblclick()');
4490 NETDATA.resetAllCharts(state);
4492 wheel: function(event, dygraph, context) {
4493 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4494 state.log('interactionModel.wheel()');
4496 // Take the offset of a mouse event on the dygraph canvas and
4497 // convert it to a pair of percentages from the bottom left.
4498 // (Not top left, bottom is where the lower value is.)
4499 function offsetToPercentage(g, offsetX, offsetY) {
4500 // This is calculating the pixel offset of the leftmost date.
4501 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4502 var yar0 = g.yAxisRange(0);
4504 // This is calculating the pixel of the higest value. (Top pixel)
4505 var yOffset = g.toDomCoords(null, yar0[1])[1];
4507 // x y w and h are relative to the corner of the drawing area,
4508 // so that the upper corner of the drawing area is (0, 0).
4509 var x = offsetX - xOffset;
4510 var y = offsetY - yOffset;
4512 // This is computing the rightmost pixel, effectively defining the
4514 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4516 // This is computing the lowest pixel, effectively defining the height.
4517 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4519 // Percentage from the left.
4520 var xPct = w === 0 ? 0 : (x / w);
4521 // Percentage from the top.
4522 var yPct = h === 0 ? 0 : (y / h);
4524 // The (1-) part below changes it from "% distance down from the top"
4525 // to "% distance up from the bottom".
4526 return [xPct, (1-yPct)];
4529 // Adjusts [x, y] toward each other by zoomInPercentage%
4530 // Split it so the left/bottom axis gets xBias/yBias of that change and
4531 // tight/top gets (1-xBias)/(1-yBias) of that change.
4533 // If a bias is missing it splits it down the middle.
4534 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4535 xBias = xBias || 0.5;
4536 yBias = yBias || 0.5;
4538 function adjustAxis(axis, zoomInPercentage, bias) {
4539 var delta = axis[1] - axis[0];
4540 var increment = delta * zoomInPercentage;
4541 var foo = [increment * bias, increment * (1-bias)];
4543 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4546 var yAxes = g.yAxisRanges();
4548 for (var i = 0; i < yAxes.length; i++) {
4549 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4552 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4555 if(event.altKey || event.shiftKey) {
4556 state.dygraph_user_action = true;
4558 state.globalSelectionSyncStop();
4559 state.globalSelectionSyncDelay();
4561 // http://dygraphs.com/gallery/interaction-api.js
4563 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4565 normal_def = event.wheelDelta / 40;
4568 normal_def = event.deltaY * -1.2;
4570 var normal = (event.detail) ? event.detail * -1 : normal_def;
4571 var percentage = normal / 50;
4573 if (!(event.offsetX && event.offsetY)){
4574 event.offsetX = event.layerX - event.target.offsetLeft;
4575 event.offsetY = event.layerY - event.target.offsetTop;
4578 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4579 var xPct = percentages[0];
4580 var yPct = percentages[1];
4582 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4583 var after = new_x_range[0];
4584 var before = new_x_range[1];
4586 var first = state.netdata_first + state.data_update_every;
4587 var last = state.netdata_last + state.data_update_every;
4590 after -= (before - last);
4597 state.setMode('zoom');
4598 if(state.updateChartPanOrZoom(after, before) === true)
4599 dygraph.updateOptions({ dateWindow: [ after, before ] });
4601 event.preventDefault();
4604 touchstart: function(event, dygraph, context) {
4605 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4606 state.log('interactionModel.touchstart()');
4608 state.dygraph_user_action = true;
4609 state.setMode('zoom');
4612 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4614 // we overwrite the touch directions at the end, to overwrite
4615 // the internal default of dygraphs
4616 context.touchDirections = { x: true, y: false };
4618 state.dygraph_last_touch_start = Date.now();
4619 state.dygraph_last_touch_move = 0;
4621 if(typeof event.touches[0].pageX === 'number')
4622 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4624 state.dygraph_last_touch_page_x = 0;
4626 touchmove: function(event, dygraph, context) {
4627 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4628 state.log('interactionModel.touchmove()');
4630 state.dygraph_user_action = true;
4631 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4633 state.dygraph_last_touch_move = Date.now();
4635 touchend: function(event, dygraph, context) {
4636 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4637 state.log('interactionModel.touchend()');
4639 state.dygraph_user_action = true;
4640 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4642 // if it didn't move, it is a selection
4643 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4644 // internal api of dygraphs
4645 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4646 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4647 if(NETDATA.dygraphSetSelection(state, t) === true)
4648 state.globalSelectionSync(t);
4651 // if it was double tap within double click time, reset the charts
4652 var now = Date.now();
4653 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4654 if(state.dygraph_last_touch_move === 0) {
4655 var dt = now - state.dygraph_last_touch_end;
4656 if(dt <= NETDATA.options.current.double_click_speed)
4657 NETDATA.resetAllCharts(state);
4661 // remember the timestamp of the last touch end
4662 state.dygraph_last_touch_end = now;
4667 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4668 state.dygraph_options.drawGrid = false;
4669 state.dygraph_options.drawAxis = false;
4670 state.dygraph_options.title = undefined;
4671 state.dygraph_options.ylabel = undefined;
4672 state.dygraph_options.yLabelWidth = 0;
4673 state.dygraph_options.labelsDivWidth = 120;
4674 state.dygraph_options.labelsDivStyles.width = '120px';
4675 state.dygraph_options.labelsSeparateLines = true;
4676 state.dygraph_options.rightGap = 0;
4677 state.dygraph_options.yRangePad = 1;
4680 if(smooth === true) {
4681 state.dygraph_smooth_eligible = true;
4683 if(NETDATA.options.current.smooth_plot === true)
4684 state.dygraph_options.plotter = smoothPlotter;
4686 else state.dygraph_smooth_eligible = false;
4688 state.dygraph_instance = new Dygraph(state.element_chart,
4689 data.result.data, state.dygraph_options);
4691 state.dygraph_force_zoom = false;
4692 state.dygraph_user_action = false;
4693 state.dygraph_last_rendered = Date.now();
4695 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4696 state.__commonMin = self.data('common-min') || null;
4697 state.__commonMax = self.data('common-max') || null;
4700 state.log('incompatible version of dygraphs detected');
4701 state.__commonMin = null;
4702 state.__commonMax = null;
4708 // ----------------------------------------------------------------------------------------------------------------
4711 NETDATA.morrisInitialize = function(callback) {
4712 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4714 // morris requires raphael
4715 if(!NETDATA.chartLibraries.raphael.initialized) {
4716 if(NETDATA.chartLibraries.raphael.enabled) {
4717 NETDATA.raphaelInitialize(function() {
4718 NETDATA.morrisInitialize(callback);
4722 NETDATA.chartLibraries.morris.enabled = false;
4723 if(typeof callback === "function")
4728 NETDATA._loadCSS(NETDATA.morris_css);
4731 url: NETDATA.morris_js,
4734 xhrFields: { withCredentials: true } // required for the cookie
4737 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4740 NETDATA.chartLibraries.morris.enabled = false;
4741 NETDATA.error(100, NETDATA.morris_js);
4743 .always(function() {
4744 if(typeof callback === "function")
4750 NETDATA.chartLibraries.morris.enabled = false;
4751 if(typeof callback === "function")
4756 NETDATA.morrisChartUpdate = function(state, data) {
4757 state.morris_instance.setData(data.result.data);
4761 NETDATA.morrisChartCreate = function(state, data) {
4763 state.morris_options = {
4764 element: state.element_chart.id,
4765 data: data.result.data,
4767 ykeys: data.dimension_names,
4768 labels: data.dimension_names,
4774 continuousLine: false,
4775 behaveLikeLine: false
4778 if(state.chart.chart_type === 'line')
4779 state.morris_instance = new Morris.Line(state.morris_options);
4781 else if(state.chart.chart_type === 'area') {
4782 state.morris_options.behaveLikeLine = true;
4783 state.morris_instance = new Morris.Area(state.morris_options);
4786 state.morris_instance = new Morris.Area(state.morris_options);
4791 // ----------------------------------------------------------------------------------------------------------------
4794 NETDATA.raphaelInitialize = function(callback) {
4795 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4797 url: NETDATA.raphael_js,
4800 xhrFields: { withCredentials: true } // required for the cookie
4803 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4806 NETDATA.chartLibraries.raphael.enabled = false;
4807 NETDATA.error(100, NETDATA.raphael_js);
4809 .always(function() {
4810 if(typeof callback === "function")
4815 NETDATA.chartLibraries.raphael.enabled = false;
4816 if(typeof callback === "function")
4821 NETDATA.raphaelChartUpdate = function(state, data) {
4822 $(state.element_chart).raphael(data.result, {
4823 width: state.chartWidth(),
4824 height: state.chartHeight()
4830 NETDATA.raphaelChartCreate = function(state, data) {
4831 $(state.element_chart).raphael(data.result, {
4832 width: state.chartWidth(),
4833 height: state.chartHeight()
4839 // ----------------------------------------------------------------------------------------------------------------
4842 NETDATA.c3Initialize = function(callback) {
4843 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4846 if(!NETDATA.chartLibraries.d3.initialized) {
4847 if(NETDATA.chartLibraries.d3.enabled) {
4848 NETDATA.d3Initialize(function() {
4849 NETDATA.c3Initialize(callback);
4853 NETDATA.chartLibraries.c3.enabled = false;
4854 if(typeof callback === "function")
4859 NETDATA._loadCSS(NETDATA.c3_css);
4865 xhrFields: { withCredentials: true } // required for the cookie
4868 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4871 NETDATA.chartLibraries.c3.enabled = false;
4872 NETDATA.error(100, NETDATA.c3_js);
4874 .always(function() {
4875 if(typeof callback === "function")
4881 NETDATA.chartLibraries.c3.enabled = false;
4882 if(typeof callback === "function")
4887 NETDATA.c3ChartUpdate = function(state, data) {
4888 state.c3_instance.destroy();
4889 return NETDATA.c3ChartCreate(state, data);
4891 //state.c3_instance.load({
4892 // rows: data.result,
4899 NETDATA.c3ChartCreate = function(state, data) {
4901 state.element_chart.id = 'c3-' + state.uuid;
4902 // console.log('id = ' + state.element_chart.id);
4904 state.c3_instance = c3.generate({
4905 bindto: '#' + state.element_chart.id,
4907 width: state.chartWidth(),
4908 height: state.chartHeight()
4911 pattern: state.chartColors()
4916 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4922 format: function(x) {
4923 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4950 // console.log(state.c3_instance);
4955 // ----------------------------------------------------------------------------------------------------------------
4958 NETDATA.d3Initialize = function(callback) {
4959 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4964 xhrFields: { withCredentials: true } // required for the cookie
4967 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4970 NETDATA.chartLibraries.d3.enabled = false;
4971 NETDATA.error(100, NETDATA.d3_js);
4973 .always(function() {
4974 if(typeof callback === "function")
4979 NETDATA.chartLibraries.d3.enabled = false;
4980 if(typeof callback === "function")
4985 NETDATA.d3ChartUpdate = function(state, data) {
4989 NETDATA.d3ChartCreate = function(state, data) {
4993 // ----------------------------------------------------------------------------------------------------------------
4996 NETDATA.googleInitialize = function(callback) {
4997 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4999 url: NETDATA.google_js,
5002 xhrFields: { withCredentials: true } // required for the cookie
5005 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5006 google.load('visualization', '1.1', {
5007 'packages': ['corechart', 'controls'],
5008 'callback': callback
5012 NETDATA.chartLibraries.google.enabled = false;
5013 NETDATA.error(100, NETDATA.google_js);
5014 if(typeof callback === "function")
5019 NETDATA.chartLibraries.google.enabled = false;
5020 if(typeof callback === "function")
5025 NETDATA.googleChartUpdate = function(state, data) {
5026 var datatable = new google.visualization.DataTable(data.result);
5027 state.google_instance.draw(datatable, state.google_options);
5031 NETDATA.googleChartCreate = function(state, data) {
5032 var datatable = new google.visualization.DataTable(data.result);
5034 state.google_options = {
5035 colors: state.chartColors(),
5037 // do not set width, height - the chart resizes itself
5038 //width: state.chartWidth(),
5039 //height: state.chartHeight(),
5044 // title: "Time of Day",
5045 // format:'HH:mm:ss',
5046 viewWindowMode: 'maximized',
5058 viewWindowMode: 'pretty',
5073 focusTarget: 'category',
5080 titlePosition: 'out',
5091 curveType: 'function',
5096 switch(state.chart.chart_type) {
5098 state.google_options.vAxis.viewWindowMode = 'maximized';
5099 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5100 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5104 state.google_options.isStacked = true;
5105 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5106 state.google_options.vAxis.viewWindowMode = 'maximized';
5107 state.google_options.vAxis.minValue = null;
5108 state.google_options.vAxis.maxValue = null;
5109 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5114 state.google_options.lineWidth = 2;
5115 state.google_instance = new google.visualization.LineChart(state.element_chart);
5119 state.google_instance.draw(datatable, state.google_options);
5123 // ----------------------------------------------------------------------------------------------------------------
5125 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5126 if(typeof value !== 'number') value = 0;
5127 if(typeof min !== 'number') min = 0;
5128 if(typeof max !== 'number') max = 0;
5130 if(min > value) min = value;
5131 if(max < value) max = value;
5133 // make sure it is zero based
5134 if(min > 0) min = 0;
5135 if(max < 0) max = 0;
5140 pcent = Math.round(value * 100 / max);
5141 if(pcent === 0) pcent = 0.1;
5145 pcent = Math.round(-value * 100 / min);
5146 if(pcent === 0) pcent = -0.1;
5152 // ----------------------------------------------------------------------------------------------------------------
5155 NETDATA.easypiechartInitialize = function(callback) {
5156 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5158 url: NETDATA.easypiechart_js,
5161 xhrFields: { withCredentials: true } // required for the cookie
5164 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5167 NETDATA.chartLibraries.easypiechart.enabled = false;
5168 NETDATA.error(100, NETDATA.easypiechart_js);
5170 .always(function() {
5171 if(typeof callback === "function")
5176 NETDATA.chartLibraries.easypiechart.enabled = false;
5177 if(typeof callback === "function")
5182 NETDATA.easypiechartClearSelection = function(state) {
5183 if(typeof state.easyPieChartEvent !== 'undefined') {
5184 if(state.easyPieChartEvent.timer !== null)
5185 clearTimeout(state.easyPieChartEvent.timer);
5187 state.easyPieChartEvent.timer = null;
5190 if(state.isAutoRefreshable() === true && state.data !== null) {
5191 NETDATA.easypiechartChartUpdate(state, state.data);
5194 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5195 state.easyPieChart_instance.update(0);
5197 state.easyPieChart_instance.enableAnimation();
5202 NETDATA.easypiechartSetSelection = function(state, t) {
5203 if(state.timeIsVisible(t) !== true)
5204 return NETDATA.easypiechartClearSelection(state);
5206 var slot = state.calculateRowForTime(t);
5207 if(slot < 0 || slot >= state.data.result.length)
5208 return NETDATA.easypiechartClearSelection(state);
5210 if(typeof state.easyPieChartEvent === 'undefined') {
5211 state.easyPieChartEvent = {
5218 var value = state.data.result[state.data.result.length - 1 - slot];
5219 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5220 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5221 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5223 state.easyPieChartEvent.value = value;
5224 state.easyPieChartEvent.pcent = pcent;
5225 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5227 if(state.easyPieChartEvent.timer === null) {
5228 state.easyPieChart_instance.disableAnimation();
5230 state.easyPieChartEvent.timer = setTimeout(function() {
5231 state.easyPieChartEvent.timer = null;
5232 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5233 }, NETDATA.options.current.charts_selection_animation_delay);
5239 NETDATA.easypiechartChartUpdate = function(state, data) {
5240 var value, min, max, pcent;
5242 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5247 value = data.result[0];
5248 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5249 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5250 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5253 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5254 state.easyPieChart_instance.update(pcent);
5258 NETDATA.easypiechartChartCreate = function(state, data) {
5259 var self = $(state.element);
5260 var chart = $(state.element_chart);
5262 var value = data.result[0];
5263 var min = self.data('easypiechart-min-value') || null;
5264 var max = self.data('easypiechart-max-value') || null;
5265 var adjust = self.data('easypiechart-adjust') || null;
5268 min = NETDATA.commonMin.get(state);
5269 state.easyPieChartMin = null;
5272 state.easyPieChartMin = min;
5275 max = NETDATA.commonMax.get(state);
5276 state.easyPieChartMax = null;
5279 state.easyPieChartMax = max;
5281 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5283 chart.data('data-percent', pcent);
5287 case 'width': size = state.chartHeight(); break;
5288 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5289 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5291 default: size = state.chartWidth(); break;
5293 state.element.style.width = size + 'px';
5294 state.element.style.height = size + 'px';
5296 var stroke = Math.floor(size / 22);
5297 if(stroke < 3) stroke = 2;
5299 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5300 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5301 state.easyPieChartLabel = document.createElement('span');
5302 state.easyPieChartLabel.className = 'easyPieChartLabel';
5303 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5304 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5305 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5306 state.element_chart.appendChild(state.easyPieChartLabel);
5308 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5309 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5310 state.easyPieChartTitle = document.createElement('span');
5311 state.easyPieChartTitle.className = 'easyPieChartTitle';
5312 state.easyPieChartTitle.innerText = state.title;
5313 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5314 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5315 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5316 state.element_chart.appendChild(state.easyPieChartTitle);
5318 var unitfontsize = Math.round(titlefontsize * 0.9);
5319 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5320 state.easyPieChartUnits = document.createElement('span');
5321 state.easyPieChartUnits.className = 'easyPieChartUnits';
5322 state.easyPieChartUnits.innerText = state.units;
5323 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5324 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5325 state.element_chart.appendChild(state.easyPieChartUnits);
5327 var barColor = self.data('easypiechart-barcolor');
5328 if(typeof barColor === 'undefined' || barColor === null)
5329 barColor = state.chartColors()[0];
5331 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5332 var tmp = eval(barColor);
5333 if(typeof tmp === 'function')
5337 chart.easyPieChart({
5339 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5340 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5341 scaleLength: self.data('easypiechart-scalelength') || 5,
5342 lineCap: self.data('easypiechart-linecap') || 'round',
5343 lineWidth: self.data('easypiechart-linewidth') || stroke,
5344 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5345 size: self.data('easypiechart-size') || size,
5346 rotate: self.data('easypiechart-rotate') || 0,
5347 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5348 easing: self.data('easypiechart-easing') || undefined
5351 // when we just re-create the chart
5352 // do not animate the first update
5354 if(typeof state.easyPieChart_instance !== 'undefined')
5357 state.easyPieChart_instance = chart.data('easyPieChart');
5358 if(animate === false) state.easyPieChart_instance.disableAnimation();
5359 state.easyPieChart_instance.update(pcent);
5360 if(animate === false) state.easyPieChart_instance.enableAnimation();
5364 // ----------------------------------------------------------------------------------------------------------------
5367 NETDATA.gaugeInitialize = function(callback) {
5368 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5370 url: NETDATA.gauge_js,
5373 xhrFields: { withCredentials: true } // required for the cookie
5376 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5379 NETDATA.chartLibraries.gauge.enabled = false;
5380 NETDATA.error(100, NETDATA.gauge_js);
5382 .always(function() {
5383 if(typeof callback === "function")
5388 NETDATA.chartLibraries.gauge.enabled = false;
5389 if(typeof callback === "function")
5394 NETDATA.gaugeAnimation = function(state, status) {
5397 if(typeof status === 'boolean' && status === false)
5399 else if(typeof status === 'number')
5402 // console.log('gauge speed ' + speed);
5403 state.gauge_instance.animationSpeed = speed;
5404 state.___gaugeOld__.speed = speed;
5407 NETDATA.gaugeSet = function(state, value, min, max) {
5408 if(typeof value !== 'number') value = 0;
5409 if(typeof min !== 'number') min = 0;
5410 if(typeof max !== 'number') max = 0;
5411 if(value > max) max = value;
5412 if(value < min) min = value;
5421 // gauge.js has an issue if the needle
5422 // is smaller than min or larger than max
5423 // when we set the new values
5424 // the needle will go crazy
5426 // to prevent it, we always feed it
5427 // with a percentage, so that the needle
5428 // is always between min and max
5429 var pcent = (value - min) * 100 / (max - min);
5431 // these should never happen
5432 if(pcent < 0) pcent = 0;
5433 if(pcent > 100) pcent = 100;
5435 state.gauge_instance.set(pcent);
5436 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5438 state.___gaugeOld__.value = value;
5439 state.___gaugeOld__.min = min;
5440 state.___gaugeOld__.max = max;
5443 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5444 if(state.___gaugeOld__.valueLabel !== value) {
5445 state.___gaugeOld__.valueLabel = value;
5446 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5448 if(state.___gaugeOld__.minLabel !== min) {
5449 state.___gaugeOld__.minLabel = min;
5450 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5452 if(state.___gaugeOld__.maxLabel !== max) {
5453 state.___gaugeOld__.maxLabel = max;
5454 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5458 NETDATA.gaugeClearSelection = function(state) {
5459 if(typeof state.gaugeEvent !== 'undefined') {
5460 if(state.gaugeEvent.timer !== null)
5461 clearTimeout(state.gaugeEvent.timer);
5463 state.gaugeEvent.timer = null;
5466 if(state.isAutoRefreshable() === true && state.data !== null) {
5467 NETDATA.gaugeChartUpdate(state, state.data);
5470 NETDATA.gaugeAnimation(state, false);
5471 NETDATA.gaugeSet(state, null, null, null);
5472 NETDATA.gaugeSetLabels(state, null, null, null);
5475 NETDATA.gaugeAnimation(state, true);
5479 NETDATA.gaugeSetSelection = function(state, t) {
5480 if(state.timeIsVisible(t) !== true)
5481 return NETDATA.gaugeClearSelection(state);
5483 var slot = state.calculateRowForTime(t);
5484 if(slot < 0 || slot >= state.data.result.length)
5485 return NETDATA.gaugeClearSelection(state);
5487 if(typeof state.gaugeEvent === 'undefined') {
5488 state.gaugeEvent = {
5496 var value = state.data.result[state.data.result.length - 1 - slot];
5497 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5498 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5500 // make sure it is zero based
5501 if(min > 0) min = 0;
5502 if(max < 0) max = 0;
5504 state.gaugeEvent.value = value;
5505 state.gaugeEvent.min = min;
5506 state.gaugeEvent.max = max;
5507 NETDATA.gaugeSetLabels(state, value, min, max);
5509 if(state.gaugeEvent.timer === null) {
5510 NETDATA.gaugeAnimation(state, false);
5512 state.gaugeEvent.timer = setTimeout(function() {
5513 state.gaugeEvent.timer = null;
5514 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5515 }, NETDATA.options.current.charts_selection_animation_delay);
5521 NETDATA.gaugeChartUpdate = function(state, data) {
5522 var value, min, max;
5524 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5528 NETDATA.gaugeSetLabels(state, null, null, null);
5531 value = data.result[0];
5532 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5533 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5534 if(value < min) min = value;
5535 if(value > max) max = value;
5537 // make sure it is zero based
5538 if(min > 0) min = 0;
5539 if(max < 0) max = 0;
5541 NETDATA.gaugeSetLabels(state, value, min, max);
5544 NETDATA.gaugeSet(state, value, min, max);
5548 NETDATA.gaugeChartCreate = function(state, data) {
5549 var self = $(state.element);
5550 // var chart = $(state.element_chart);
5552 var value = data.result[0];
5553 var min = self.data('gauge-min-value') || null;
5554 var max = self.data('gauge-max-value') || null;
5555 var adjust = self.data('gauge-adjust') || null;
5556 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5557 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5558 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5559 var stopColor = self.data('gauge-stop-color') || void 0;
5560 var generateGradient = self.data('gauge-generate-gradient') || false;
5563 min = NETDATA.commonMin.get(state);
5564 state.gaugeMin = null;
5567 state.gaugeMin = min;
5570 max = NETDATA.commonMax.get(state);
5571 state.gaugeMax = null;
5574 state.gaugeMax = max;
5576 // make sure it is zero based
5577 if(min > 0) min = 0;
5578 if(max < 0) max = 0;
5580 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5582 // case 'width': width = height * ratio; break;
5584 // default: height = width / ratio; break;
5586 //state.element.style.width = width.toString() + 'px';
5587 //state.element.style.height = height.toString() + 'px';
5592 lines: 12, // The number of lines to draw
5593 angle: 0.15, // The length of each line
5594 lineWidth: 0.44, // 0.44 The line thickness
5596 length: 0.8, // 0.9 The radius of the inner circle
5597 strokeWidth: 0.035, // The rotation offset
5598 color: pointerColor // Fill color
5600 colorStart: startColor, // Colors
5601 colorStop: stopColor, // just experiment with them
5602 strokeColor: strokeColor, // to see which ones work best for you
5604 generateGradient: (generateGradient === true)?true:false,
5608 if (generateGradient.constructor === Array) {
5610 // data-gauge-generate-gradient="[0, 50, 100]"
5611 // data-gauge-gradient-percent-color-0="#FFFFFF"
5612 // data-gauge-gradient-percent-color-50="#999900"
5613 // data-gauge-gradient-percent-color-100="#000000"
5615 options.percentColors = new Array();
5616 var len = generateGradient.length;
5618 var pcent = generateGradient[len];
5619 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5620 if(color !== false) {
5621 var a = new Array();
5624 options.percentColors.unshift(a);
5627 if(options.percentColors.length === 0)
5628 delete options.percentColors;
5630 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5631 options.percentColors = [
5632 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5633 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5634 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5635 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5636 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5637 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5638 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5639 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5640 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5641 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5642 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5645 state.gauge_canvas = document.createElement('canvas');
5646 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5647 state.gauge_canvas.className = 'gaugeChart';
5648 state.gauge_canvas.width = width;
5649 state.gauge_canvas.height = height;
5650 state.element_chart.appendChild(state.gauge_canvas);
5652 var valuefontsize = Math.floor(height / 6);
5653 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5654 state.gaugeChartLabel = document.createElement('span');
5655 state.gaugeChartLabel.className = 'gaugeChartLabel';
5656 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5657 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5658 state.element_chart.appendChild(state.gaugeChartLabel);
5660 var titlefontsize = Math.round(valuefontsize / 2);
5662 state.gaugeChartTitle = document.createElement('span');
5663 state.gaugeChartTitle.className = 'gaugeChartTitle';
5664 state.gaugeChartTitle.innerText = state.title;
5665 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5666 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5667 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5668 state.element_chart.appendChild(state.gaugeChartTitle);
5670 var unitfontsize = Math.round(titlefontsize * 0.9);
5671 state.gaugeChartUnits = document.createElement('span');
5672 state.gaugeChartUnits.className = 'gaugeChartUnits';
5673 state.gaugeChartUnits.innerText = state.units;
5674 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5675 state.element_chart.appendChild(state.gaugeChartUnits);
5677 state.gaugeChartMin = document.createElement('span');
5678 state.gaugeChartMin.className = 'gaugeChartMin';
5679 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5680 state.element_chart.appendChild(state.gaugeChartMin);
5682 state.gaugeChartMax = document.createElement('span');
5683 state.gaugeChartMax.className = 'gaugeChartMax';
5684 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5685 state.element_chart.appendChild(state.gaugeChartMax);
5687 // when we just re-create the chart
5688 // do not animate the first update
5690 if(typeof state.gauge_instance !== 'undefined')
5693 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5695 state.___gaugeOld__ = {
5704 // we will always feed a percentage
5705 state.gauge_instance.minValue = 0;
5706 state.gauge_instance.maxValue = 100;
5708 NETDATA.gaugeAnimation(state, animate);
5709 NETDATA.gaugeSet(state, value, min, max);
5710 NETDATA.gaugeSetLabels(state, value, min, max);
5711 NETDATA.gaugeAnimation(state, true);
5715 // ----------------------------------------------------------------------------------------------------------------
5716 // Charts Libraries Registration
5718 NETDATA.chartLibraries = {
5720 initialize: NETDATA.dygraphInitialize,
5721 create: NETDATA.dygraphChartCreate,
5722 update: NETDATA.dygraphChartUpdate,
5723 resize: function(state) {
5724 if(typeof state.dygraph_instance.resize === 'function')
5725 state.dygraph_instance.resize();
5727 setSelection: NETDATA.dygraphSetSelection,
5728 clearSelection: NETDATA.dygraphClearSelection,
5729 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5732 format: function(state) { return 'json'; },
5733 options: function(state) { return 'ms|flip'; },
5734 legend: function(state) {
5735 if(this.isSparkline(state) === false)
5736 return 'right-side';
5740 autoresize: function(state) { return true; },
5741 max_updates_to_recreate: function(state) { return 5000; },
5742 track_colors: function(state) { return true; },
5743 pixels_per_point: function(state) {
5744 if(this.isSparkline(state) === false)
5750 isSparkline: function(state) {
5751 if(typeof state.dygraph_sparkline === 'undefined') {
5752 var t = $(state.element).data('dygraph-theme');
5753 if(t === 'sparkline')
5754 state.dygraph_sparkline = true;
5756 state.dygraph_sparkline = false;
5758 return state.dygraph_sparkline;
5762 initialize: NETDATA.sparklineInitialize,
5763 create: NETDATA.sparklineChartCreate,
5764 update: NETDATA.sparklineChartUpdate,
5766 setSelection: undefined, // function(state, t) { return true; },
5767 clearSelection: undefined, // function(state) { return true; },
5768 toolboxPanAndZoom: null,
5771 format: function(state) { return 'array'; },
5772 options: function(state) { return 'flip|abs'; },
5773 legend: function(state) { return null; },
5774 autoresize: function(state) { return false; },
5775 max_updates_to_recreate: function(state) { return 5000; },
5776 track_colors: function(state) { return false; },
5777 pixels_per_point: function(state) { return 3; }
5780 initialize: NETDATA.peityInitialize,
5781 create: NETDATA.peityChartCreate,
5782 update: NETDATA.peityChartUpdate,
5784 setSelection: undefined, // function(state, t) { return true; },
5785 clearSelection: undefined, // function(state) { return true; },
5786 toolboxPanAndZoom: null,
5789 format: function(state) { return 'ssvcomma'; },
5790 options: function(state) { return 'null2zero|flip|abs'; },
5791 legend: function(state) { return null; },
5792 autoresize: function(state) { return false; },
5793 max_updates_to_recreate: function(state) { return 5000; },
5794 track_colors: function(state) { return false; },
5795 pixels_per_point: function(state) { return 3; }
5798 initialize: NETDATA.morrisInitialize,
5799 create: NETDATA.morrisChartCreate,
5800 update: NETDATA.morrisChartUpdate,
5802 setSelection: undefined, // function(state, t) { return true; },
5803 clearSelection: undefined, // function(state) { return true; },
5804 toolboxPanAndZoom: null,
5807 format: function(state) { return 'json'; },
5808 options: function(state) { return 'objectrows|ms'; },
5809 legend: function(state) { return null; },
5810 autoresize: function(state) { return false; },
5811 max_updates_to_recreate: function(state) { return 50; },
5812 track_colors: function(state) { return false; },
5813 pixels_per_point: function(state) { return 15; }
5816 initialize: NETDATA.googleInitialize,
5817 create: NETDATA.googleChartCreate,
5818 update: NETDATA.googleChartUpdate,
5820 setSelection: undefined, //function(state, t) { return true; },
5821 clearSelection: undefined, //function(state) { return true; },
5822 toolboxPanAndZoom: null,
5825 format: function(state) { return 'datatable'; },
5826 options: function(state) { return ''; },
5827 legend: function(state) { return null; },
5828 autoresize: function(state) { return false; },
5829 max_updates_to_recreate: function(state) { return 300; },
5830 track_colors: function(state) { return false; },
5831 pixels_per_point: function(state) { return 4; }
5834 initialize: NETDATA.raphaelInitialize,
5835 create: NETDATA.raphaelChartCreate,
5836 update: NETDATA.raphaelChartUpdate,
5838 setSelection: undefined, // function(state, t) { return true; },
5839 clearSelection: undefined, // function(state) { return true; },
5840 toolboxPanAndZoom: null,
5843 format: function(state) { return 'json'; },
5844 options: function(state) { return ''; },
5845 legend: function(state) { return null; },
5846 autoresize: function(state) { return false; },
5847 max_updates_to_recreate: function(state) { return 5000; },
5848 track_colors: function(state) { return false; },
5849 pixels_per_point: function(state) { return 3; }
5852 initialize: NETDATA.c3Initialize,
5853 create: NETDATA.c3ChartCreate,
5854 update: NETDATA.c3ChartUpdate,
5856 setSelection: undefined, // function(state, t) { return true; },
5857 clearSelection: undefined, // function(state) { return true; },
5858 toolboxPanAndZoom: null,
5861 format: function(state) { return 'csvjsonarray'; },
5862 options: function(state) { return 'milliseconds'; },
5863 legend: function(state) { return null; },
5864 autoresize: function(state) { return false; },
5865 max_updates_to_recreate: function(state) { return 5000; },
5866 track_colors: function(state) { return false; },
5867 pixels_per_point: function(state) { return 15; }
5870 initialize: NETDATA.d3Initialize,
5871 create: NETDATA.d3ChartCreate,
5872 update: NETDATA.d3ChartUpdate,
5874 setSelection: undefined, // function(state, t) { return true; },
5875 clearSelection: undefined, // function(state) { return true; },
5876 toolboxPanAndZoom: null,
5879 format: function(state) { return 'json'; },
5880 options: function(state) { return ''; },
5881 legend: function(state) { return null; },
5882 autoresize: function(state) { return false; },
5883 max_updates_to_recreate: function(state) { return 5000; },
5884 track_colors: function(state) { return false; },
5885 pixels_per_point: function(state) { return 3; }
5888 initialize: NETDATA.easypiechartInitialize,
5889 create: NETDATA.easypiechartChartCreate,
5890 update: NETDATA.easypiechartChartUpdate,
5892 setSelection: NETDATA.easypiechartSetSelection,
5893 clearSelection: NETDATA.easypiechartClearSelection,
5894 toolboxPanAndZoom: null,
5897 format: function(state) { return 'array'; },
5898 options: function(state) { return 'absolute'; },
5899 legend: function(state) { return null; },
5900 autoresize: function(state) { return false; },
5901 max_updates_to_recreate: function(state) { return 5000; },
5902 track_colors: function(state) { return true; },
5903 pixels_per_point: function(state) { return 3; },
5907 initialize: NETDATA.gaugeInitialize,
5908 create: NETDATA.gaugeChartCreate,
5909 update: NETDATA.gaugeChartUpdate,
5911 setSelection: NETDATA.gaugeSetSelection,
5912 clearSelection: NETDATA.gaugeClearSelection,
5913 toolboxPanAndZoom: null,
5916 format: function(state) { return 'array'; },
5917 options: function(state) { return 'absolute'; },
5918 legend: function(state) { return null; },
5919 autoresize: function(state) { return false; },
5920 max_updates_to_recreate: function(state) { return 5000; },
5921 track_colors: function(state) { return true; },
5922 pixels_per_point: function(state) { return 3; },
5927 NETDATA.registerChartLibrary = function(library, url) {
5928 if(NETDATA.options.debug.libraries === true)
5929 console.log("registering chart library: " + library);
5931 NETDATA.chartLibraries[library].url = url;
5932 NETDATA.chartLibraries[library].initialized = true;
5933 NETDATA.chartLibraries[library].enabled = true;
5936 // ----------------------------------------------------------------------------------------------------------------
5937 // Load required JS libraries and CSS
5939 NETDATA.requiredJs = [
5941 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5943 isAlreadyLoaded: function() {
5944 // check if bootstrap is loaded
5945 if(typeof $().emulateTransitionEnd == 'function')
5948 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5956 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5957 isAlreadyLoaded: function() { return false; }
5961 NETDATA.requiredCSS = [
5963 url: NETDATA.themes.current.bootstrap_css,
5964 isAlreadyLoaded: function() {
5965 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5972 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5973 isAlreadyLoaded: function() { return false; }
5976 url: NETDATA.themes.current.dashboard_css,
5977 isAlreadyLoaded: function() { return false; }
5981 NETDATA.loadedRequiredJs = 0;
5982 NETDATA.loadRequiredJs = function(index, callback) {
5983 if(index >= NETDATA.requiredJs.length) {
5984 if(typeof callback === 'function')
5989 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5990 NETDATA.loadedRequiredJs++;
5991 NETDATA.loadRequiredJs(++index, callback);
5995 if(NETDATA.options.debug.main_loop === true)
5996 console.log('loading ' + NETDATA.requiredJs[index].url);
5999 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6003 url: NETDATA.requiredJs[index].url,
6006 xhrFields: { withCredentials: true } // required for the cookie
6009 if(NETDATA.options.debug.main_loop === true)
6010 console.log('loaded ' + NETDATA.requiredJs[index].url);
6013 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6015 .always(function() {
6016 NETDATA.loadedRequiredJs++;
6019 NETDATA.loadRequiredJs(++index, callback);
6023 NETDATA.loadRequiredJs(++index, callback);
6026 NETDATA.loadRequiredCSS = function(index) {
6027 if(index >= NETDATA.requiredCSS.length)
6030 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6031 NETDATA.loadRequiredCSS(++index);
6035 if(NETDATA.options.debug.main_loop === true)
6036 console.log('loading ' + NETDATA.requiredCSS[index].url);
6038 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6039 NETDATA.loadRequiredCSS(++index);
6043 // ----------------------------------------------------------------------------------------------------------------
6044 // Registry of netdata hosts
6047 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6048 chart_div_offset: 100, // give that space above the chart when scrolling to it
6049 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6050 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6052 ms_penalty: 0, // the time penalty of the next alarm
6053 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6054 // if alarms are shown faster than: one per 500ms
6056 notifications: false, // when true, the browser supports notifications (may not be granted though)
6057 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6058 first_notification_id: 0, // the id of the first alarm_log entry for this session
6059 // this is used to prevent CLEAR notifications for past events
6060 // notifications_shown: new Array(),
6062 server: null, // the server to connect to for fetching alarms
6063 current: null, // the list of raised alarms - updated in the background
6064 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6066 notify: function(entry) {
6067 // console.log('alarm ' + entry.unique_id);
6069 if(entry.updated === true) {
6070 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6074 var value = entry.value;
6075 if(NETDATA.alarms.current !== null) {
6076 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6077 if(typeof t !== 'undefined' && entry.status == t.status)
6081 var name = entry.name.replace(/_/g, ' ');
6082 var status = entry.status.toLowerCase();
6083 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6084 var tag = entry.alarm_id;
6085 var icon = 'images/seo-performance-128.png';
6086 var interaction = false;
6090 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6092 switch(entry.status) {
6100 case 'UNINITIALIZED':
6104 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6105 // console.log('alarm ' + entry.unique_id + ' is not current');
6108 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6109 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6112 title = name + ' back to normal';
6113 icon = 'images/check-mark-2-128-green.png'
6114 interaction = false;
6118 if(entry.old_status === 'CRITICAL')
6119 status = 'demoted to ' + entry.status.toLowerCase();
6121 icon = 'images/alert-128-orange.png';
6122 interaction = false;
6126 if(entry.old_status === 'WARNING')
6127 status = 'escalated to ' + entry.status.toLowerCase();
6129 icon = 'images/alert-128-red.png'
6134 console.log('invalid alarm status ' + entry.status);
6139 // cleanup old notifications with the same alarm_id as this one
6140 // FIXME: it does not seem to work on any web browser!
6141 var len = NETDATA.alarms.notifications_shown.length;
6143 var n = NETDATA.alarms.notifications_shown[len];
6144 if(n.data.alarm_id === entry.alarm_id) {
6145 console.log('removing old alarm ' + n.data.unique_id);
6147 // close the notification
6150 // remove it from the array
6151 NETDATA.alarms.notifications_shown.splice(len, 1);
6152 len = NETDATA.alarms.notifications_shown.length;
6159 setTimeout(function() {
6160 // show this notification
6161 // console.log('new notification: ' + title);
6162 var n = new Notification(title, {
6163 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6165 requireInteraction: interaction,
6166 icon: NETDATA.serverDefault + icon,
6170 n.onclick = function(event) {
6171 event.preventDefault();
6172 NETDATA.alarms.onclick(event.target.data);
6176 // NETDATA.alarms.notifications_shown.push(n);
6177 // console.log(entry);
6178 }, NETDATA.alarms.ms_penalty);
6180 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6184 scrollToChart: function(chart_id) {
6185 if(typeof chart_id === 'string') {
6186 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6187 if(typeof offset !== 'undefined') {
6188 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6195 scrollToAlarm: function(alarm) {
6196 if(typeof alarm === 'object') {
6197 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6199 if(ret === true && NETDATA.options.page_is_visible === false)
6201 // 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.');
6206 notifyAll: function() {
6207 // console.log('FETCHING ALARM LOG');
6208 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6209 // console.log('ALARM LOG FETCHED');
6211 if(data === null || typeof data !== 'object') {
6212 console.log('invalid alarms log response');
6216 if(data.length === 0) {
6217 console.log('received empty alarm log');
6221 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6223 data.sort(function(a, b) {
6224 if(a.unique_id > b.unique_id) return -1;
6225 if(a.unique_id < b.unique_id) return 1;
6229 NETDATA.alarms.ms_penalty = 0;
6231 var len = data.length;
6233 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6234 NETDATA.alarms.notify(data[len]);
6237 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6240 NETDATA.alarms.last_notification_id = data[0].unique_id;
6241 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6242 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6246 check_notifications: function() {
6247 // returns true if we should fire 1+ notifications
6249 if(NETDATA.alarms.notifications !== true) {
6250 // console.log('notifications not available');
6254 if(Notification.permission !== 'granted') {
6255 // console.log('notifications not granted');
6259 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6260 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6262 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6263 // console.log('new alarms detected');
6266 //else console.log('no new alarms');
6268 // else console.log('cannot process alarms');
6273 get: function(what, callback) {
6275 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6279 'Cache-Control': 'no-cache, no-store',
6280 'Pragma': 'no-cache'
6282 xhrFields: { withCredentials: true } // required for the cookie
6284 .done(function(data) {
6285 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6286 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6288 if(typeof callback === 'function')
6292 NETDATA.error(415, NETDATA.alarms.server);
6294 if(typeof callback === 'function')
6299 update_forever: function() {
6300 NETDATA.alarms.get('active', function(data) {
6302 NETDATA.alarms.current = data;
6304 if(NETDATA.alarms.check_notifications() === true) {
6305 NETDATA.alarms.notifyAll();
6308 if (typeof NETDATA.alarms.callback === 'function') {
6309 NETDATA.alarms.callback(data);
6312 // Health monitoring is disabled on this netdata
6313 if(data.status === false) return;
6316 setTimeout(NETDATA.alarms.update_forever, 10000);
6320 get_log: function(last_id, callback) {
6321 // console.log('fetching all log after ' + last_id.toString());
6323 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6327 'Cache-Control': 'no-cache, no-store',
6328 'Pragma': 'no-cache'
6330 xhrFields: { withCredentials: true } // required for the cookie
6332 .done(function(data) {
6333 if(typeof callback === 'function')
6337 NETDATA.error(416, NETDATA.alarms.server);
6339 if(typeof callback === 'function')
6345 var host = NETDATA.serverDefault;
6346 while(host.slice(-1) === '/')
6347 host = host.substring(0, host.length - 1);
6348 NETDATA.alarms.server = host;
6350 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6352 if(NETDATA.alarms.onclick === null)
6353 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6355 if(netdataShowAlarms === true) {
6356 NETDATA.alarms.update_forever();
6358 if('Notification' in window) {
6359 // console.log('notifications available');
6360 NETDATA.alarms.notifications = true;
6362 if(Notification.permission === 'default')
6363 Notification.requestPermission();
6369 // ----------------------------------------------------------------------------------------------------------------
6370 // Registry of netdata hosts
6372 NETDATA.registry = {
6373 server: null, // the netdata registry server
6374 person_guid: null, // the unique ID of this browser / user
6375 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6376 hostname: null, // the hostname of the netdata server that served dashboard.js
6377 machines: null, // the user's other URLs
6378 machines_array: null, // the user's other URLs in an array
6381 parsePersonUrls: function(person_urls) {
6382 // console.log(person_urls);
6383 NETDATA.registry.person_urls = person_urls;
6386 NETDATA.registry.machines = {};
6387 NETDATA.registry.machines_array = new Array();
6389 var now = Date.now();
6390 var apu = person_urls;
6393 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6394 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6400 accesses: apu[i][3],
6402 alternate_urls: new Array()
6404 obj.alternate_urls.push(apu[i][1]);
6406 NETDATA.registry.machines[apu[i][0]] = obj;
6407 NETDATA.registry.machines_array.push(obj);
6410 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6412 var pu = NETDATA.registry.machines[apu[i][0]];
6413 if(pu.last_t < apu[i][2]) {
6415 pu.last_t = apu[i][2];
6416 pu.name = apu[i][4];
6418 pu.accesses += apu[i][3];
6419 pu.alternate_urls.push(apu[i][1]);
6424 if(typeof netdataRegistryCallback === 'function')
6425 netdataRegistryCallback(NETDATA.registry.machines_array);
6429 if(netdataRegistry !== true) return;
6431 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6433 NETDATA.registry.server = data.registry;
6434 NETDATA.registry.machine_guid = data.machine_guid;
6435 NETDATA.registry.hostname = data.hostname;
6437 NETDATA.registry.access(2, function (person_urls) {
6438 NETDATA.registry.parsePersonUrls(person_urls);
6445 hello: function(host, callback) {
6446 while(host.slice(-1) === '/')
6447 host = host.substring(0, host.length - 1);
6449 // send HELLO to a netdata server:
6450 // 1. verifies the server is reachable
6451 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6453 url: host + '/api/v1/registry?action=hello',
6457 'Cache-Control': 'no-cache, no-store',
6458 'Pragma': 'no-cache'
6460 xhrFields: { withCredentials: true } // required for the cookie
6462 .done(function(data) {
6463 if(typeof data.status !== 'string' || data.status !== 'ok') {
6464 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6468 if(typeof callback === 'function')
6472 NETDATA.error(407, host);
6474 if(typeof callback === 'function')
6479 access: function(max_redirects, callback) {
6480 // send ACCESS to a netdata registry:
6481 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6482 // 2. it responds with a list of netdata servers we know
6483 // the registry identifies us using a cookie it sets the first time we access it
6484 // the registry may respond with a redirect URL to send us to another registry
6486 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),
6490 'Cache-Control': 'no-cache, no-store',
6491 'Pragma': 'no-cache'
6493 xhrFields: { withCredentials: true } // required for the cookie
6495 .done(function(data) {
6496 var redirect = null;
6497 if(typeof data.registry === 'string')
6498 redirect = data.registry;
6500 if(typeof data.status !== 'string' || data.status !== 'ok') {
6501 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6506 if(redirect !== null && max_redirects > 0) {
6507 NETDATA.registry.server = redirect;
6508 NETDATA.registry.access(max_redirects - 1, callback);
6511 if(typeof callback === 'function')
6516 if(typeof data.person_guid === 'string')
6517 NETDATA.registry.person_guid = data.person_guid;
6519 if(typeof callback === 'function')
6520 callback(data.urls);
6524 NETDATA.error(410, NETDATA.registry.server);
6526 if(typeof callback === 'function')
6531 delete: function(delete_url, callback) {
6532 // send DELETE to a netdata registry:
6534 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),
6538 'Cache-Control': 'no-cache, no-store',
6539 'Pragma': 'no-cache'
6541 xhrFields: { withCredentials: true } // required for the cookie
6543 .done(function(data) {
6544 if(typeof data.status !== 'string' || data.status !== 'ok') {
6545 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6549 if(typeof callback === 'function')
6553 NETDATA.error(412, NETDATA.registry.server);
6555 if(typeof callback === 'function')
6560 search: function(machine_guid, callback) {
6561 // SEARCH for the URLs of a machine:
6563 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,
6567 'Cache-Control': 'no-cache, no-store',
6568 'Pragma': 'no-cache'
6570 xhrFields: { withCredentials: true } // required for the cookie
6572 .done(function(data) {
6573 if(typeof data.status !== 'string' || data.status !== 'ok') {
6574 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6578 if(typeof callback === 'function')
6582 NETDATA.error(418, NETDATA.registry.server);
6584 if(typeof callback === 'function')
6589 switch: function(new_person_guid, callback) {
6592 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,
6596 'Cache-Control': 'no-cache, no-store',
6597 'Pragma': 'no-cache'
6599 xhrFields: { withCredentials: true } // required for the cookie
6601 .done(function(data) {
6602 if(typeof data.status !== 'string' || data.status !== 'ok') {
6603 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6607 if(typeof callback === 'function')
6611 NETDATA.error(414, NETDATA.registry.server);
6613 if(typeof callback === 'function')
6619 // ----------------------------------------------------------------------------------------------------------------
6622 if(typeof netdataPrepCallback === 'function')
6623 netdataPrepCallback();
6625 NETDATA.errorReset();
6626 NETDATA.loadRequiredCSS(0);
6628 NETDATA._loadjQuery(function() {
6629 NETDATA.loadRequiredJs(0, function() {
6630 if(typeof $().emulateTransitionEnd !== 'function') {
6631 // bootstrap is not available
6632 NETDATA.options.current.show_help = false;
6635 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6636 if(NETDATA.options.debug.main_loop === true)
6637 console.log('starting chart refresh thread');
6643 })(window, document);