1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
33 var NETDATA = window.NETDATA || {};
35 (function(window, document, undefined) {
36 // ------------------------------------------------------------------------
37 // compatibility fixes
39 // fix IE issue with console
40 if(!window.console) { window.console = { log: function(){} }; }
42 // if string.endsWith is not defined, define it
43 if(typeof String.prototype.endsWith !== 'function') {
44 String.prototype.endsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(-s.length) === s;
50 // if string.startsWith is not defined, define it
51 if(typeof String.prototype.startsWith !== 'function') {
52 String.prototype.startsWith = function(s) {
53 if(s.length > this.length) return false;
54 return this.slice(s.length) === s;
58 NETDATA.name2id = function(s) {
67 // ----------------------------------------------------------------------------------------------------------------
68 // Detect the netdata server
70 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
71 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
72 NETDATA._scriptSource = function() {
75 if(typeof document.currentScript !== 'undefined') {
76 script = document.currentScript;
79 var all_scripts = document.getElementsByTagName('script');
80 script = all_scripts[all_scripts.length - 1];
83 if (typeof script.getAttribute.length !== 'undefined')
86 script = script.getAttribute('src', -1);
91 if(typeof netdataServer !== 'undefined')
92 NETDATA.serverDefault = netdataServer;
94 var s = NETDATA._scriptSource();
95 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97 console.log('WARNING: Cannot detect the URL of the netdata server.');
98 NETDATA.serverDefault = null;
102 if(NETDATA.serverDefault === null)
103 NETDATA.serverDefault = '';
104 else if(NETDATA.serverDefault.slice(-1) !== '/')
105 NETDATA.serverDefault += '/';
107 // default URLs for all the external files we need
108 // make them RELATIVE so that the whole thing can also be
109 // installed under a web server
110 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
111 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
112 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
113 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
114 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
115 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
116 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
117 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
118 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
119 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
120 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
121 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
122 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
123 NETDATA.google_js = 'https://www.google.com/jsapi';
127 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
128 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
129 background: '#FFFFFF',
130 foreground: '#000000',
133 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
134 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
135 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
136 '#329262', '#3B3EAC' ],
137 easypiechart_track: '#f0f0f0',
138 easypiechart_scale: '#dfe0e0',
139 gauge_pointer: '#C0C0C0',
140 gauge_stroke: '#F0F0F0',
141 gauge_gradient: false
144 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
145 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
146 background: '#272b30',
147 foreground: '#C8C8C8',
150 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
151 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
152 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
153 '#a6a479', '#a66da8' ],
155 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
156 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
157 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
158 '#329262', '#3B3EFF' ],
159 easypiechart_track: '#373b40',
160 easypiechart_scale: '#373b40',
161 gauge_pointer: '#474b50',
162 gauge_stroke: '#373b40',
163 gauge_gradient: false
167 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
168 NETDATA.themes.current = NETDATA.themes[netdataTheme];
170 NETDATA.themes.current = NETDATA.themes.white;
172 NETDATA.colors = NETDATA.themes.current.colors;
174 // these are the colors Google Charts are using
175 // we have them here to attempt emulate their look and feel on the other chart libraries
176 // http://there4.io/2012/05/02/google-chart-color-list/
177 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
178 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
179 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181 // an alternative set
182 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
183 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
184 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186 if(typeof netdataShowHelp === 'undefined')
187 netdataShowHelp = true;
189 if(typeof netdataShowAlarms === 'undefined')
190 netdataShowAlarms = false;
192 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
193 netdataRegistryAfterMs = 1500;
195 if(typeof netdataRegistry === 'undefined') {
196 // backward compatibility
197 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
198 netdataRegistry = true;
200 netdataRegistry = false;
202 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
203 netdataRegistry = true;
205 // ----------------------------------------------------------------------------------------------------------------
206 // the defaults for all charts
208 // if the user does not specify any of these, the following will be used
210 NETDATA.chartDefaults = {
211 host: NETDATA.serverDefault, // the server to get data from
212 width: '100%', // the chart width - can be null
213 height: '100%', // the chart height - can be null
214 min_width: null, // the chart minimum width - can be null
215 library: 'dygraph', // the graphing library to use
216 method: 'average', // the grouping method
217 before: 0, // panning
218 after: -600, // panning
219 pixels_per_point: 1, // the detail of the chart
220 fill_luminance: 0.8 // luminance of colors in solit areas
223 // ----------------------------------------------------------------------------------------------------------------
227 pauseCallback: null, // a callback when we are really paused
229 pause: false, // when enabled we don't auto-refresh the charts
231 targets: null, // an array of all the state objects that are
232 // currently active (independently of their
233 // viewport visibility)
235 updated_dom: true, // when true, the DOM has been updated with
236 // new elements we have to check.
238 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
239 // rendering charts continiously.
240 // used with .current.fast_render_timeframe
242 page_is_visible: true, // when true, this page is visible
244 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
245 // auto-refresher for some time (when a chart is
246 // performing pan or zoom, we need to stop refreshing
247 // all other charts, to have the maximum speed for
248 // rendering the chart that is panned or zoomed).
249 // Used with .current.global_pan_sync_time
251 last_resized: Date.now(), // the timestamp of the last resize request
253 last_page_scroll: 0, // the timestamp the last time the page was scrolled
255 // the current profile
256 // we may have many...
258 pixels_per_point: 1, // the minimum pixels per point for all charts
259 // increase this to speed javascript up
260 // each chart library has its own limit too
261 // the max of this and the chart library is used
262 // the final is calculated every time, so a change
263 // here will have immediate effect on the next chart
266 idle_between_charts: 100, // ms - how much time to wait between chart updates
268 fast_render_timeframe: 200, // ms - render continously until this time of continious
269 // rendering has been reached
270 // this setting is used to make it render e.g. 10
271 // charts at once, sleep idle_between_charts time
272 // and continue for another 10 charts.
274 idle_between_loops: 500, // ms - if all charts have been updated, wait this
275 // time before starting again.
277 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
279 idle_lost_focus: 500, // ms - when the window does not have focus, check
280 // if focus has been regained, every this time
282 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
283 // autorefreshing of charts is paused for this amount
286 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
287 // of time before setting up synchronized selections
290 sync_selection: true, // enable or disable selection sync
292 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
294 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
296 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298 update_only_visible: true, // enable or disable visibility management
300 parallel_refresher: true, // enable parallel refresh of charts
302 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304 destroy_on_hide: false, // destroy charts when they are not visible
306 show_help: netdataShowHelp, // when enabled the charts will show some help
307 show_help_delay_show_ms: 500,
308 show_help_delay_hide_ms: 0,
310 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
313 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
315 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
317 smooth_plot: true, // enable smooth plot, where possible
319 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321 color_fill_opacity_line: 1.0,
322 color_fill_opacity_area: 0.2,
323 color_fill_opacity_stacked: 0.8,
325 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
326 pan_and_zoom_factor_multiplier_control: 2.0,
327 pan_and_zoom_factor_multiplier_shift: 3.0,
328 pan_and_zoom_factor_multiplier_alt: 4.0,
330 abort_ajax_on_scroll: false, // kill pending ajax page scroll
331 async_on_scroll: false, // sync/async onscroll handler
332 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
336 setOptionCallback: function() { ; }
344 chart_data_url: false,
345 chart_errors: false, // FIXME
353 NETDATA.statistics = {
356 refreshes_active_max: 0
360 // ----------------------------------------------------------------------------------------------------------------
361 // local storage options
363 NETDATA.localStorage = {
366 callback: {} // only used for resetting back to defaults
369 NETDATA.localStorageGet = function(key, def, callback) {
372 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
373 NETDATA.localStorage.default[key.toString()] = def;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
379 // console.log('localStorage: loading "' + key.toString() + '"');
380 ret = localStorage.getItem(key.toString());
381 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
382 if(ret === null || ret === 'undefined') {
383 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
384 localStorage.setItem(key.toString(), JSON.stringify(def));
388 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
389 ret = JSON.parse(ret);
390 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
394 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
399 if(typeof ret === 'undefined' || ret === 'undefined') {
400 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
404 NETDATA.localStorage.current[key.toString()] = ret;
408 NETDATA.localStorageSet = function(key, value, callback) {
409 if(typeof value === 'undefined' || value === 'undefined') {
410 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
413 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
414 NETDATA.localStorage.default[key.toString()] = value;
415 NETDATA.localStorage.current[key.toString()] = value;
416 NETDATA.localStorage.callback[key.toString()] = callback;
419 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
420 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(value));
425 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
429 NETDATA.localStorage.current[key.toString()] = value;
433 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
435 if(typeof obj[i] === 'object') {
436 //console.log('object ' + prefix + '.' + i.toString());
437 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
441 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
445 NETDATA.setOption = function(key, value) {
446 if(key.toString() === 'setOptionCallback') {
447 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
448 NETDATA.options.current[key.toString()] = value;
449 NETDATA.options.current.setOptionCallback();
452 else if(NETDATA.options.current[key.toString()] !== value) {
453 var name = 'options.' + key.toString();
455 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
456 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
458 //console.log(NETDATA.localStorage);
459 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
460 //console.log(NETDATA.options);
461 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
463 if(typeof NETDATA.options.current.setOptionCallback === 'function')
464 NETDATA.options.current.setOptionCallback();
470 NETDATA.getOption = function(key) {
471 return NETDATA.options.current[key.toString()];
474 // read settings from local storage
475 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
477 // always start with this option enabled.
478 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
480 NETDATA.resetOptions = function() {
481 for(var i in NETDATA.localStorage.default) {
482 var a = i.split('.');
484 if(a[0] === 'options') {
485 if(a[1] === 'setOptionCallback') continue;
486 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
487 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
489 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
491 else if(a[0] === 'chart_heights') {
492 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
493 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
499 // ----------------------------------------------------------------------------------------------------------------
501 if(NETDATA.options.debug.main_loop === true)
502 console.log('welcome to NETDATA');
504 NETDATA.onresizeCallback = null;
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
509 if(typeof NETDATA.onresizeCallback === 'function')
510 NETDATA.onresizeCallback();
513 NETDATA.onscroll_updater_count = 0;
514 NETDATA.onscroll_updater_running = false;
515 NETDATA.onscroll_updater_last_run = 0;
516 NETDATA.onscroll_updater_watchdog = null;
517 NETDATA.onscroll_updater_max_duration = 0;
518 NETDATA.onscroll_updater_above_threshold_count = 0;
519 NETDATA.onscroll_updater = function() {
520 NETDATA.onscroll_updater_running = true;
521 NETDATA.onscroll_updater_count++;
522 var start = Date.now();
524 var targets = NETDATA.options.targets;
525 var len = targets.length;
527 // when the user scrolls he sees that we have
528 // hidden all the not-visible charts
529 // using this little function we try to switch
530 // the charts back to visible quickly
533 if(NETDATA.options.abort_ajax_on_scroll === true) {
534 // we have to cancel pending requests too
537 if (targets[len]._updating === true) {
538 if (typeof targets[len].xhr !== 'undefined') {
539 targets[len].xhr.abort();
540 targets[len].running = false;
541 targets[len]._updating = false;
543 targets[len].isVisible();
548 // just find which chart is visible
551 targets[len].isVisible();
554 var end = Date.now();
555 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
557 if(NETDATA.options.current.async_on_scroll === false) {
558 var dt = end - start;
559 if(dt > NETDATA.onscroll_updater_max_duration) {
560 // console.log('max onscroll event handler duration increased to ' + dt);
561 NETDATA.onscroll_updater_max_duration = dt;
564 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
565 // console.log('slow: ' + dt);
566 NETDATA.onscroll_updater_above_threshold_count++;
568 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
569 NETDATA.setOption('async_on_scroll', true);
570 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
575 NETDATA.onscroll_updater_last_run = start;
576 NETDATA.onscroll_updater_running = false;
579 NETDATA.onscroll = function() {
580 // console.log('onscroll');
582 NETDATA.options.last_page_scroll = Date.now();
583 NETDATA.options.auto_refresher_stop_until = 0;
585 if(NETDATA.options.targets === null) return;
587 if(NETDATA.options.current.async_on_scroll === true) {
589 if(NETDATA.onscroll_updater_running === false) {
590 NETDATA.onscroll_updater_running = true;
591 setTimeout(NETDATA.onscroll_updater, 0);
594 if(NETDATA.onscroll_updater_watchdog !== null)
595 clearTimeout(NETDATA.onscroll_updater_watchdog);
597 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
598 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
599 // console.log('watchdog');
600 NETDATA.onscroll_updater();
603 NETDATA.onscroll_updater_watchdog = null;
609 NETDATA.onscroll_updater();
613 window.onresize = NETDATA.onresize;
614 window.onscroll = NETDATA.onscroll;
616 // ----------------------------------------------------------------------------------------------------------------
619 NETDATA.errorCodes = {
620 100: { message: "Cannot load chart library", alert: true },
621 101: { message: "Cannot load jQuery", alert: true },
622 402: { message: "Chart library not found", alert: false },
623 403: { message: "Chart library not enabled/is failed", alert: false },
624 404: { message: "Chart not found", alert: false },
625 405: { message: "Cannot download charts index from server", alert: true },
626 406: { message: "Invalid charts index downloaded from server", alert: true },
627 407: { message: "Cannot HELLO netdata server", alert: false },
628 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
629 409: { message: "Cannot ACCESS netdata registry", alert: false },
630 410: { message: "Netdata registry ACCESS failed", alert: false },
631 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
632 412: { message: "Netdata registry DELETE failed", alert: false },
633 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
634 414: { message: "Netdata registry SWITCH failed", alert: false },
635 415: { message: "Netdata alarms download failed", alert: false },
636 416: { message: "Netdata alarms log download failed", alert: false },
637 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
638 418: { message: "Netdata registry SEARCH failed", alert: false }
640 NETDATA.errorLast = {
646 NETDATA.error = function(code, msg) {
647 NETDATA.errorLast.code = code;
648 NETDATA.errorLast.message = msg;
649 NETDATA.errorLast.datetime = Date.now();
651 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
654 if(typeof netdataErrorCallback === 'function') {
655 ret = netdataErrorCallback('system', code, msg);
658 if(ret && NETDATA.errorCodes[code].alert)
659 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
662 NETDATA.errorReset = function() {
663 NETDATA.errorLast.code = 0;
664 NETDATA.errorLast.message = "You are doing fine!";
665 NETDATA.errorLast.datetime = 0;
668 // ----------------------------------------------------------------------------------------------------------------
669 // commonMin & commonMax
671 NETDATA.commonMin = {
675 get: function(state) {
676 if(typeof state.__commonMin === 'undefined') {
677 // get the commonMin setting
678 var self = $(state.element);
679 state.__commonMin = self.data('common-min') || null;
682 var min = state.data.min;
683 var name = state.__commonMin;
686 // we don't need commonMin
687 //state.log('no need for commonMin');
691 var t = this.keys[name];
692 if(typeof t === 'undefined') {
694 this.keys[name] = {};
698 var uuid = state.uuid;
699 if(typeof t[uuid] !== 'undefined') {
700 if(t[uuid] === min) {
701 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
702 return this.latest[name];
704 else if(min < this.latest[name]) {
705 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
707 this.latest[name] = min;
715 // find the common min
718 if(t[i] < m) m = t[i];
720 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
721 this.latest[name] = m;
726 NETDATA.commonMax = {
730 get: function(state) {
731 if(typeof state.__commonMax === 'undefined') {
732 // get the commonMax setting
733 var self = $(state.element);
734 state.__commonMax = self.data('common-max') || null;
737 var max = state.data.max;
738 var name = state.__commonMax;
741 // we don't need commonMax
742 //state.log('no need for commonMax');
746 var t = this.keys[name];
747 if(typeof t === 'undefined') {
749 this.keys[name] = {};
753 var uuid = state.uuid;
754 if(typeof t[uuid] !== 'undefined') {
755 if(t[uuid] === max) {
756 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
757 return this.latest[name];
759 else if(max > this.latest[name]) {
760 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
762 this.latest[name] = max;
770 // find the common max
773 if(t[i] > m) m = t[i];
775 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
776 this.latest[name] = m;
781 // ----------------------------------------------------------------------------------------------------------------
784 // When multiple charts need the same chart, we avoid downloading it
785 // multiple times (and having it in browser memory multiple time)
786 // by using this registry.
788 // Every time we download a chart definition, we save it here with .add()
789 // Then we try to get it back with .get(). If that fails, we download it.
791 NETDATA.chartRegistry = {
794 fixid: function(id) {
795 return id.replace(/:/g, "_").replace(/\//g, "_");
798 add: function(host, id, data) {
799 host = this.fixid(host);
802 if(typeof this.charts[host] === 'undefined')
803 this.charts[host] = {};
805 //console.log('added ' + host + '/' + id);
806 this.charts[host][id] = data;
809 get: function(host, id) {
810 host = this.fixid(host);
813 if(typeof this.charts[host] === 'undefined')
816 if(typeof this.charts[host][id] === 'undefined')
819 //console.log('cached ' + host + '/' + id);
820 return this.charts[host][id];
823 downloadAll: function(host, callback) {
824 while(host.slice(-1) === '/')
825 host = host.substring(0, host.length - 1);
830 url: host + '/api/v1/charts',
833 xhrFields: { withCredentials: true } // required for the cookie
835 .done(function(data) {
837 var h = NETDATA.chartRegistry.fixid(host);
838 self.charts[h] = data.charts;
840 else NETDATA.error(406, host + '/api/v1/charts');
842 if(typeof callback === 'function')
846 NETDATA.error(405, host + '/api/v1/charts');
848 if(typeof callback === 'function')
854 // ----------------------------------------------------------------------------------------------------------------
855 // Global Pan and Zoom on charts
857 // Using this structure are synchronize all the charts, so that
858 // when you pan or zoom one, all others are automatically refreshed
859 // to the same timespan.
861 NETDATA.globalPanAndZoom = {
862 seq: 0, // timestamp ms
863 // every time a chart is panned or zoomed
864 // we set the timestamp here
865 // then we use it as a sequence number
866 // to find if other charts are syncronized
869 master: null, // the master chart (state), to which all others
872 force_before_ms: null, // the timespan to sync all other charts
873 force_after_ms: null,
878 setMaster: function(state, after, before) {
879 if(NETDATA.options.current.sync_pan_and_zoom === false)
882 if(this.master !== null && this.master !== state)
883 this.master.resetChart(true, true);
885 var now = Date.now();
888 this.force_after_ms = after;
889 this.force_before_ms = before;
890 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
892 if(typeof this.callback === 'function')
893 this.callback(true, after, before);
897 clearMaster: function() {
898 if(this.master !== null) {
899 var st = this.master;
906 this.force_after_ms = null;
907 this.force_before_ms = null;
908 NETDATA.options.auto_refresher_stop_until = 0;
910 if(typeof this.callback === 'function')
911 this.callback(false, 0, 0);
914 // is the given state the master of the global
915 // pan and zoom sync?
916 isMaster: function(state) {
917 if(this.master === state) return true;
921 // are we currently have a global pan and zoom sync?
922 isActive: function() {
923 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
927 // check if a chart, other than the master
928 // needs to be refreshed, due to the global pan and zoom
929 shouldBeAutoRefreshed: function(state) {
930 if(this.master === null || this.seq === 0)
933 //if(state.needsRecreation())
936 if(state.tm.pan_and_zoom_seq === this.seq)
943 // ----------------------------------------------------------------------------------------------------------------
944 // dimensions selection
947 // move color assignment to dimensions, here
949 dimensionStatus = function(parent, label, name_div, value_div, color) {
950 this.enabled = false;
951 this.parent = parent;
953 this.name_div = null;
954 this.value_div = null;
955 this.color = NETDATA.themes.current.foreground;
957 if(parent.unselected_count === 0)
958 this.selected = true;
960 this.selected = false;
962 this.setOptions(name_div, value_div, color);
965 dimensionStatus.prototype.invalidate = function() {
966 this.name_div = null;
967 this.value_div = null;
968 this.enabled = false;
971 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
974 if(this.name_div != name_div) {
975 this.name_div = name_div;
976 this.name_div.title = this.label;
977 this.name_div.style.color = this.color;
978 if(this.selected === false)
979 this.name_div.className = 'netdata-legend-name not-selected';
981 this.name_div.className = 'netdata-legend-name selected';
984 if(this.value_div != value_div) {
985 this.value_div = value_div;
986 this.value_div.title = this.label;
987 this.value_div.style.color = this.color;
988 if(this.selected === false)
989 this.value_div.className = 'netdata-legend-value not-selected';
991 this.value_div.className = 'netdata-legend-value selected';
998 dimensionStatus.prototype.setHandler = function() {
999 if(this.enabled === false) return;
1003 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1004 this.name_div.onclick = this.value_div.onclick = function(e) {
1006 if(ds.isSelected()) {
1008 if(e.shiftKey === true || e.ctrlKey === true) {
1009 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1012 if(ds.parent.countSelected() === 0)
1013 ds.parent.selectAll();
1016 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1017 if(ds.parent.countSelected() === 1) {
1018 ds.parent.selectAll();
1021 ds.parent.selectNone();
1027 // this is not selected
1028 if(e.shiftKey === true || e.ctrlKey === true) {
1029 // control or shift key is pressed -> select this too
1033 // no key is pressed -> select only this
1034 ds.parent.selectNone();
1039 ds.parent.state.redrawChart();
1043 dimensionStatus.prototype.select = function() {
1044 if(this.enabled === false) return;
1046 this.name_div.className = 'netdata-legend-name selected';
1047 this.value_div.className = 'netdata-legend-value selected';
1048 this.selected = true;
1051 dimensionStatus.prototype.unselect = function() {
1052 if(this.enabled === false) return;
1054 this.name_div.className = 'netdata-legend-name not-selected';
1055 this.value_div.className = 'netdata-legend-value hidden';
1056 this.selected = false;
1059 dimensionStatus.prototype.isSelected = function() {
1060 return(this.enabled === true && this.selected === true);
1063 // ----------------------------------------------------------------------------------------------------------------
1065 dimensionsVisibility = function(state) {
1068 this.dimensions = {};
1069 this.selected_count = 0;
1070 this.unselected_count = 0;
1073 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1074 if(typeof this.dimensions[label] === 'undefined') {
1076 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1079 this.dimensions[label].setOptions(name_div, value_div, color);
1081 return this.dimensions[label];
1084 dimensionsVisibility.prototype.dimensionGet = function(label) {
1085 return this.dimensions[label];
1088 dimensionsVisibility.prototype.invalidateAll = function() {
1089 for(var d in this.dimensions)
1090 this.dimensions[d].invalidate();
1093 dimensionsVisibility.prototype.selectAll = function() {
1094 for(var d in this.dimensions)
1095 this.dimensions[d].select();
1098 dimensionsVisibility.prototype.countSelected = function() {
1100 for(var d in this.dimensions)
1101 if(this.dimensions[d].isSelected()) i++;
1106 dimensionsVisibility.prototype.selectNone = function() {
1107 for(var d in this.dimensions)
1108 this.dimensions[d].unselect();
1111 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1112 var ret = new Array();
1113 this.selected_count = 0;
1114 this.unselected_count = 0;
1116 var len = array.length;
1118 var ds = this.dimensions[array[len]];
1119 if(typeof ds === 'undefined') {
1120 // console.log(array[i] + ' is not found');
1123 else if(ds.isSelected()) {
1125 this.selected_count++;
1129 this.unselected_count++;
1133 if(this.selected_count === 0 && this.unselected_count !== 0) {
1135 return this.selected2BooleanArray(array);
1142 // ----------------------------------------------------------------------------------------------------------------
1143 // global selection sync
1145 NETDATA.globalSelectionSync = {
1147 dont_sync_before: 0,
1152 if(this.state !== null)
1153 this.state.globalSelectionSyncStop();
1157 if(this.state !== null) {
1158 this.state.globalSelectionSyncDelay();
1163 // ----------------------------------------------------------------------------------------------------------------
1164 // Our state object, where all per-chart values are stored
1166 chartState = function(element) {
1167 var self = $(element);
1168 this.element = element;
1171 // all private functions should use 'that', instead of 'this'
1174 /* error() - private
1175 * show an error instead of the chart
1177 var error = function(msg) {
1180 if(typeof netdataErrorCallback === 'function') {
1181 ret = netdataErrorCallback('chart', that.id, msg);
1185 that.element.innerHTML = that.id + ': ' + msg;
1186 that.enabled = false;
1187 that.current = that.pan;
1191 // GUID - a unique identifier for the chart
1192 this.uuid = NETDATA.guid();
1194 // string - the name of chart
1195 this.id = self.data('netdata');
1197 // string - the key for localStorage settings
1198 this.settings_id = self.data('id') || null;
1200 // the user given dimensions of the element
1201 this.width = self.data('width') || NETDATA.chartDefaults.width;
1202 this.height = self.data('height') || NETDATA.chartDefaults.height;
1203 this.height_original = this.height;
1205 if(this.settings_id !== null) {
1206 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1207 // this is the callback that will be called
1208 // if and when the user resets all localStorage variables
1209 // to their defaults
1211 resizeChartToHeight(height);
1215 // string - the netdata server URL, without any path
1216 this.host = self.data('host') || NETDATA.chartDefaults.host;
1218 // make sure the host does not end with /
1219 // all netdata API requests use absolute paths
1220 while(this.host.slice(-1) === '/')
1221 this.host = this.host.substring(0, this.host.length - 1);
1223 // string - the grouping method requested by the user
1224 this.method = self.data('method') || NETDATA.chartDefaults.method;
1226 // the time-range requested by the user
1227 this.after = self.data('after') || NETDATA.chartDefaults.after;
1228 this.before = self.data('before') || NETDATA.chartDefaults.before;
1230 // the pixels per point requested by the user
1231 this.pixels_per_point = self.data('pixels-per-point') || 1;
1232 this.points = self.data('points') || null;
1234 // the dimensions requested by the user
1235 this.dimensions = self.data('dimensions') || null;
1237 // the chart library requested by the user
1238 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1240 // how many retries we have made to load chart data from the server
1241 this.retries_on_data_failures = 0;
1243 // object - the chart library used
1244 this.library = null;
1248 this.colors_assigned = {};
1249 this.colors_available = null;
1251 // the element already created by the user
1252 this.element_message = null;
1254 // the element with the chart
1255 this.element_chart = null;
1257 // the element with the legend of the chart (if created by us)
1258 this.element_legend = null;
1259 this.element_legend_childs = {
1264 perfect_scroller: null, // the container to apply perfect scroller to
1268 this.chart_url = null; // string - the url to download chart info
1269 this.chart = null; // object - the chart as downloaded from the server
1271 this.title = self.data('title') || null; // the title of the chart
1272 this.units = self.data('units') || null; // the units of the chart dimensions
1273 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1274 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1276 this.running = false; // boolean - true when the chart is being refreshed now
1277 this.validated = false; // boolean - has the chart been validated?
1278 this.enabled = true; // boolean - is the chart enabled for refresh?
1279 this.paused = false; // boolean - is the chart paused for any reason?
1280 this.selected = false; // boolean - is the chart shown a selection?
1281 this.debug = false; // boolean - console.log() debug info about this chart
1283 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1284 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1285 this.requested_after = null; // milliseconds - the timestamp of the request after param
1286 this.requested_before = null; // milliseconds - the timestamp of the request before param
1287 this.requested_padding = null;
1288 this.view_after = 0;
1289 this.view_before = 0;
1291 this.value_decimal_detail = -1;
1293 var d = self.data('decimal-digits');
1294 if(typeof d === 'number') {
1295 this.value_decimal_detail = 1;
1297 this.value_decimal_detail *= 10;
1304 force_update_at: 0, // the timestamp to force the update at
1305 force_before_ms: null,
1306 force_after_ms: null
1311 force_update_at: 0, // the timestamp to force the update at
1312 force_before_ms: null,
1313 force_after_ms: null
1318 force_update_at: 0, // the timestamp to force the update at
1319 force_before_ms: null,
1320 force_after_ms: null
1323 // this is a pointer to one of the sub-classes below
1325 this.current = this.auto;
1327 // check the requested library is available
1328 // we don't initialize it here - it will be initialized when
1329 // this chart will be first used
1330 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1331 NETDATA.error(402, that.library_name);
1332 error('chart library "' + that.library_name + '" is not found');
1335 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1336 NETDATA.error(403, that.library_name);
1337 error('chart library "' + that.library_name + '" is not enabled');
1341 that.library = NETDATA.chartLibraries[that.library_name];
1343 // milliseconds - the time the last refresh took
1344 this.refresh_dt_ms = 0;
1346 // if we need to report the rendering speed
1347 // find the element that needs to be updated
1348 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1350 if(refresh_dt_element_name !== null)
1351 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1353 this.refresh_dt_element = null;
1355 this.dimensions_visibility = new dimensionsVisibility(this);
1357 this._updating = false;
1359 // ============================================================================================================
1360 // PRIVATE FUNCTIONS
1362 var createDOM = function() {
1363 if(that.enabled === false) return;
1365 if(that.element_message !== null) that.element_message.innerHTML = '';
1366 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1367 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1369 that.element.innerHTML = '';
1371 that.element_message = document.createElement('div');
1372 that.element_message.className = 'netdata-message icon hidden';
1373 that.element.appendChild(that.element_message);
1375 that.element_chart = document.createElement('div');
1376 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1377 that.element.appendChild(that.element_chart);
1379 if(that.hasLegend() === true) {
1380 that.element.className = "netdata-container-with-legend";
1381 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1383 that.element_legend = document.createElement('div');
1384 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1385 that.element.appendChild(that.element_legend);
1388 that.element.className = "netdata-container";
1389 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1391 that.element_legend = null;
1393 that.element_legend_childs.series = null;
1395 if(typeof(that.width) === 'string')
1396 $(that.element).css('width', that.width);
1397 else if(typeof(that.width) === 'number')
1398 $(that.element).css('width', that.width + 'px');
1400 if(typeof(that.library.aspect_ratio) === 'undefined') {
1401 if(typeof(that.height) === 'string')
1402 that.element.style.height = that.height;
1403 else if(typeof(that.height) === 'number')
1404 that.element.style.height = that.height.toString() + 'px';
1407 var w = that.element.offsetWidth;
1408 if(w === null || w === 0) {
1409 // the div is hidden
1410 // this will resize the chart when next viewed
1411 that.tm.last_resized = 0;
1414 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1417 if(NETDATA.chartDefaults.min_width !== null)
1418 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1420 that.tm.last_dom_created = Date.now();
1426 * initialize state variables
1427 * destroy all (possibly) created state elements
1428 * create the basic DOM for a chart
1430 var init = function() {
1431 if(that.enabled === false) return;
1433 that.paused = false;
1434 that.selected = false;
1436 that.chart_created = false; // boolean - is the library.create() been called?
1437 that.updates_counter = 0; // numeric - the number of refreshes made so far
1438 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1439 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1442 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1443 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1444 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1446 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1447 last_updated: 0, // the timestamp the chart last updated with data
1448 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1450 // Used with NETDATA.globalPanAndZoom.seq
1451 last_visible_check: 0, // the time we last checked if it is visible
1452 last_resized: 0, // the time the chart was resized
1453 last_hidden: 0, // the time the chart was hidden
1454 last_unhidden: 0, // the time the chart was unhidden
1455 last_autorefreshed: 0 // the time the chart was last refreshed
1458 that.data = null; // the last data as downloaded from the netdata server
1459 that.data_url = 'invalid://'; // string - the last url used to update the chart
1460 that.data_points = 0; // number - the number of points returned from netdata
1461 that.data_after = 0; // milliseconds - the first timestamp of the data
1462 that.data_before = 0; // milliseconds - the last timestamp of the data
1463 that.data_update_every = 0; // milliseconds - the frequency to update the data
1465 that.tm.last_initialized = Date.now();
1468 that.setMode('auto');
1471 var maxMessageFontSize = function() {
1472 var screenHeight = screen.height;
1473 var el = that.element;
1475 // normally we want a font size, as tall as the element
1476 var h = el.clientHeight;
1478 // but give it some air, 20% let's say, or 5 pixels min
1479 var lost = Math.max(h * 0.2, 5);
1482 // center the text, vertically
1483 var paddingTop = (lost - 5) / 2;
1485 // but check the width too
1486 // it should fit 10 characters in it
1487 var w = el.clientWidth / 10;
1489 paddingTop += (h - w) / 2;
1493 // and don't make it too huge
1494 // 5% of the screen size is good
1495 if(h > screenHeight / 20) {
1496 paddingTop += (h - (screenHeight / 20)) / 2;
1497 h = screenHeight / 20;
1501 that.element_message.style.fontSize = h.toString() + 'px';
1502 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1505 var showMessageIcon = function(icon) {
1506 that.element_message.innerHTML = icon;
1507 maxMessageFontSize();
1508 $(that.element_message).removeClass('hidden');
1509 that.___messageHidden___ = undefined;
1512 var hideMessage = function() {
1513 if(typeof that.___messageHidden___ === 'undefined') {
1514 that.___messageHidden___ = true;
1515 $(that.element_message).addClass('hidden');
1519 var showRendering = function() {
1521 if(that.chart !== null) {
1522 if(that.chart.chart_type === 'line')
1523 icon = '<i class="fa fa-line-chart"></i>';
1525 icon = '<i class="fa fa-area-chart"></i>';
1528 icon = '<i class="fa fa-area-chart"></i>';
1530 showMessageIcon(icon + ' netdata');
1533 var showLoading = function() {
1534 if(that.chart_created === false) {
1535 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1541 var isHidden = function() {
1542 if(typeof that.___chartIsHidden___ !== 'undefined')
1548 // hide the chart, when it is not visible - called from isVisible()
1549 var hideChart = function() {
1550 // hide it, if it is not already hidden
1551 if(isHidden() === true) return;
1553 if(that.chart_created === true) {
1554 if(NETDATA.options.current.destroy_on_hide === true) {
1555 // we should destroy it
1560 that.element_chart.style.display = 'none';
1561 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1562 that.tm.last_hidden = Date.now();
1565 // This works, but I not sure there are no corner cases somewhere
1566 // so it is commented - if the user has memory issues he can
1567 // set Destroy on Hide for all charts
1568 // that.data = null;
1572 that.___chartIsHidden___ = true;
1575 // unhide the chart, when it is visible - called from isVisible()
1576 var unhideChart = function() {
1577 if(isHidden() === false) return;
1579 that.___chartIsHidden___ = undefined;
1580 that.updates_since_last_unhide = 0;
1582 if(that.chart_created === false) {
1583 // we need to re-initialize it, to show our background
1584 // logo in bootstrap tabs, until the chart loads
1588 that.tm.last_unhidden = Date.now();
1589 that.element_chart.style.display = '';
1590 if(that.element_legend !== null) that.element_legend.style.display = '';
1596 var canBeRendered = function() {
1597 if(isHidden() === true || that.isVisible(true) === false)
1603 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1604 var callChartLibraryUpdateSafely = function(data) {
1607 if(canBeRendered() === false)
1610 if(NETDATA.options.debug.chart_errors === true)
1611 status = that.library.update(that, data);
1614 status = that.library.update(that, data);
1621 if(status === false) {
1622 error('chart failed to be updated as ' + that.library_name);
1629 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1630 var callChartLibraryCreateSafely = function(data) {
1633 if(canBeRendered() === false)
1636 if(NETDATA.options.debug.chart_errors === true)
1637 status = that.library.create(that, data);
1640 status = that.library.create(that, data);
1647 if(status === false) {
1648 error('chart failed to be created as ' + that.library_name);
1652 that.chart_created = true;
1653 that.updates_since_last_creation = 0;
1657 // ----------------------------------------------------------------------------------------------------------------
1660 // resizeChart() - private
1661 // to be called just before the chart library to make sure that
1662 // a properly sized dom is available
1663 var resizeChart = function() {
1664 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1665 if(that.chart_created === false) return;
1667 if(that.needsRecreation()) {
1670 else if(typeof that.library.resize === 'function') {
1671 that.library.resize(that);
1673 if(that.element_legend_childs.perfect_scroller !== null)
1674 Ps.update(that.element_legend_childs.perfect_scroller);
1676 maxMessageFontSize();
1679 that.tm.last_resized = Date.now();
1683 // this is the actual chart resize algorithm
1685 // - resize the entire container
1686 // - update the internal states
1687 // - resize the chart as the div changes height
1688 // - update the scrollbar of the legend
1689 var resizeChartToHeight = function(h) {
1691 that.element.style.height = h;
1693 if(that.settings_id !== null)
1694 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1696 var now = Date.now();
1697 NETDATA.options.last_page_scroll = now;
1698 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1701 that.tm.last_resized = 0;
1705 this.resizeHandler = function(e) {
1708 if(typeof this.event_resize === 'undefined'
1709 || this.event_resize.chart_original_w === 'undefined'
1710 || this.event_resize.chart_original_h === 'undefined')
1711 this.event_resize = {
1712 chart_original_w: this.element.clientWidth,
1713 chart_original_h: this.element.clientHeight,
1717 if(e.type === 'touchstart') {
1718 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1719 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1722 this.event_resize.mouse_start_x = e.clientX;
1723 this.event_resize.mouse_start_y = e.clientY;
1726 this.event_resize.chart_start_w = this.element.clientWidth;
1727 this.event_resize.chart_start_h = this.element.clientHeight;
1728 this.event_resize.chart_last_w = this.element.clientWidth;
1729 this.event_resize.chart_last_h = this.element.clientHeight;
1731 var now = Date.now();
1732 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller != null) {
1733 // double click / double tap event
1735 // console.dir(this.element_legend_childs.content);
1736 // console.dir(this.element_legend_childs.perfect_scroller);
1738 // the optimal height of the chart
1739 // showing the entire legend
1740 var optimal = this.event_resize.chart_last_h
1741 + this.element_legend_childs.perfect_scroller.scrollHeight
1742 - this.element_legend_childs.perfect_scroller.clientHeight;
1744 // if we are not optimal, be optimal
1745 if(this.event_resize.chart_last_h != optimal) {
1746 // 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());
1747 resizeChartToHeight(optimal.toString() + 'px');
1750 // else if the current height is not the original/saved height
1751 // reset to the original/saved height
1752 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h) {
1753 // 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());
1754 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1757 // else if the current height is not the internal default height
1758 // reset to the internal default height
1759 else if((this.event_resize.chart_last_h.toString() + 'px') != this.height_original) {
1760 // 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());
1761 resizeChartToHeight(this.height_original.toString());
1764 // else if the current height is not the firstchild's clientheight
1766 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1767 var parent_rect = this.element.getBoundingClientRect();
1768 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1769 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1771 // console.log(parent_rect);
1772 // console.log(content_rect);
1773 // console.log(wanted);
1775 // 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' );
1776 if(this.event_resize.chart_last_h != wanted)
1777 resizeChartToHeight(wanted.toString() + 'px');
1781 this.event_resize.last = now;
1783 // process movement event
1784 document.onmousemove =
1785 document.ontouchmove =
1786 this.element_legend_childs.resize_handler.onmousemove =
1787 this.element_legend_childs.resize_handler.ontouchmove =
1792 case 'mousemove': y = e.clientY; break;
1793 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1797 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1799 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1800 resizeChartToHeight(newH.toString() + 'px');
1801 that.event_resize.chart_last_h = newH;
1806 // process end event
1807 document.onmouseup =
1808 document.ontouchend =
1809 this.element_legend_childs.resize_handler.onmouseup =
1810 this.element_legend_childs.resize_handler.ontouchend =
1812 // remove all the hooks
1813 document.onmouseup =
1814 document.onmousemove =
1815 document.ontouchmove =
1816 document.ontouchend =
1817 that.element_legend_childs.resize_handler.onmousemove =
1818 that.element_legend_childs.resize_handler.ontouchmove =
1819 that.element_legend_childs.resize_handler.onmouseout =
1820 that.element_legend_childs.resize_handler.onmouseup =
1821 that.element_legend_childs.resize_handler.ontouchend =
1824 // allow auto-refreshes
1825 NETDATA.options.auto_refresher_stop_until = 0;
1831 var noDataToShow = function() {
1832 showMessageIcon('<i class="fa fa-warning"></i> empty');
1833 that.legendUpdateDOM();
1834 that.tm.last_autorefreshed = Date.now();
1835 // that.data_update_every = 30 * 1000;
1836 //that.element_chart.style.display = 'none';
1837 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1838 //that.___chartIsHidden___ = true;
1841 // ============================================================================================================
1844 this.error = function(msg) {
1848 this.setMode = function(m) {
1849 if(this.current !== null && this.current.name === m) return;
1852 this.current = this.auto;
1853 else if(m === 'pan')
1854 this.current = this.pan;
1855 else if(m === 'zoom')
1856 this.current = this.zoom;
1858 this.current = this.auto;
1860 this.current.force_update_at = 0;
1861 this.current.force_before_ms = null;
1862 this.current.force_after_ms = null;
1864 this.tm.last_mode_switch = Date.now();
1867 // ----------------------------------------------------------------------------------------------------------------
1868 // global selection sync
1870 // prevent to global selection sync for some time
1871 this.globalSelectionSyncDelay = function(ms) {
1872 if(NETDATA.options.current.sync_selection === false)
1875 if(typeof ms === 'number')
1876 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1878 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1881 // can we globally apply selection sync?
1882 this.globalSelectionSyncAbility = function() {
1883 if(NETDATA.options.current.sync_selection === false)
1886 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1892 this.globalSelectionSyncIsMaster = function() {
1893 if(NETDATA.globalSelectionSync.state === this)
1899 // this chart is the master of the global selection sync
1900 this.globalSelectionSyncBeMaster = function() {
1902 if(this.globalSelectionSyncIsMaster()) {
1903 if(this.debug === true)
1904 this.log('sync: I am the master already.');
1909 if(NETDATA.globalSelectionSync.state) {
1910 if(this.debug === true)
1911 this.log('sync: I am not the sync master. Resetting global sync.');
1913 this.globalSelectionSyncStop();
1916 // become the master
1917 if(this.debug === true)
1918 this.log('sync: becoming sync master.');
1920 this.selected = true;
1921 NETDATA.globalSelectionSync.state = this;
1923 // find the all slaves
1924 var targets = NETDATA.options.targets;
1925 var len = targets.length;
1930 if(this.debug === true)
1931 st.log('sync: not adding me to sync');
1933 else if(st.globalSelectionSyncIsEligible()) {
1934 if(this.debug === true)
1935 st.log('sync: adding to sync as slave');
1937 st.globalSelectionSyncBeSlave();
1941 // this.globalSelectionSyncDelay(100);
1944 // can the chart participate to the global selection sync as a slave?
1945 this.globalSelectionSyncIsEligible = function() {
1946 if(this.enabled === true
1947 && this.library !== null
1948 && typeof this.library.setSelection === 'function'
1949 && this.isVisible() === true
1950 && this.chart_created === true)
1956 // this chart becomes a slave of the global selection sync
1957 this.globalSelectionSyncBeSlave = function() {
1958 if(NETDATA.globalSelectionSync.state !== this)
1959 NETDATA.globalSelectionSync.slaves.push(this);
1962 // sync all the visible charts to the given time
1963 // this is to be called from the chart libraries
1964 this.globalSelectionSync = function(t) {
1965 if(this.globalSelectionSyncAbility() === false) {
1966 if(this.debug === true)
1967 this.log('sync: cannot sync (yet?).');
1972 if(this.globalSelectionSyncIsMaster() === false) {
1973 if(this.debug === true)
1974 this.log('sync: trying to be sync master.');
1976 this.globalSelectionSyncBeMaster();
1978 if(this.globalSelectionSyncAbility() === false) {
1979 if(this.debug === true)
1980 this.log('sync: cannot sync (yet?).');
1986 NETDATA.globalSelectionSync.last_t = t;
1987 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1992 // stop syncing all charts to the given time
1993 this.globalSelectionSyncStop = function() {
1994 if(NETDATA.globalSelectionSync.slaves.length) {
1995 if(this.debug === true)
1996 this.log('sync: cleaning up...');
1998 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2000 if(that.debug === true)
2001 st.log('sync: not adding me to sync stop');
2004 if(that.debug === true)
2005 st.log('sync: removed slave from sync');
2007 st.clearSelection();
2011 NETDATA.globalSelectionSync.last_t = 0;
2012 NETDATA.globalSelectionSync.slaves = [];
2013 NETDATA.globalSelectionSync.state = null;
2016 this.clearSelection();
2019 this.setSelection = function(t) {
2020 if(typeof this.library.setSelection === 'function') {
2021 if(this.library.setSelection(this, t) === true)
2022 this.selected = true;
2024 this.selected = false;
2026 else this.selected = true;
2028 if(this.selected === true && this.debug === true)
2029 this.log('selection set to ' + t.toString());
2031 return this.selected;
2034 this.clearSelection = function() {
2035 if(this.selected === true) {
2036 if(typeof this.library.clearSelection === 'function') {
2037 if(this.library.clearSelection(this) === true)
2038 this.selected = false;
2040 this.selected = true;
2042 else this.selected = false;
2044 if(this.selected === false && this.debug === true)
2045 this.log('selection cleared');
2050 return this.selected;
2053 // find if a timestamp (ms) is shown in the current chart
2054 this.timeIsVisible = function(t) {
2055 if(t >= this.data_after && t <= this.data_before)
2060 this.calculateRowForTime = function(t) {
2061 if(this.timeIsVisible(t) === false) return -1;
2062 return Math.floor((t - this.data_after) / this.data_update_every);
2065 // ----------------------------------------------------------------------------------------------------------------
2068 this.log = function(msg) {
2069 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2072 this.pauseChart = function() {
2073 if(this.paused === false) {
2074 if(this.debug === true)
2075 this.log('pauseChart()');
2081 this.unpauseChart = function() {
2082 if(this.paused === true) {
2083 if(this.debug === true)
2084 this.log('unpauseChart()');
2086 this.paused = false;
2090 this.resetChart = function(dont_clear_master, dont_update) {
2091 if(this.debug === true)
2092 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2094 if(typeof dont_clear_master === 'undefined')
2095 dont_clear_master = false;
2097 if(typeof dont_update === 'undefined')
2098 dont_update = false;
2100 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2101 if(this.debug === true)
2102 this.log('resetChart() diverting to clearMaster().');
2103 // this will call us back with master === true
2104 NETDATA.globalPanAndZoom.clearMaster();
2108 this.clearSelection();
2110 this.tm.pan_and_zoom_seq = 0;
2112 this.setMode('auto');
2113 this.current.force_update_at = 0;
2114 this.current.force_before_ms = null;
2115 this.current.force_after_ms = null;
2116 this.tm.last_autorefreshed = 0;
2117 this.paused = false;
2118 this.selected = false;
2119 this.enabled = true;
2120 // this.debug = false;
2122 // do not update the chart here
2123 // or the chart will flip-flop when it is the master
2124 // of a selection sync and another chart becomes
2127 if(dont_update !== true && this.isVisible() === true) {
2132 this.updateChartPanOrZoom = function(after, before) {
2133 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2136 if(this.debug === true)
2139 if(before < after) {
2140 if(this.debug === true)
2141 this.log(logme + 'flipped parameters, rejecting it.');
2146 if(typeof this.fixed_min_duration === 'undefined')
2147 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2149 var min_duration = this.fixed_min_duration;
2150 var current_duration = Math.round(this.view_before - this.view_after);
2152 // round the numbers
2153 after = Math.round(after);
2154 before = Math.round(before);
2156 // align them to update_every
2157 // stretching them further away
2158 after -= after % this.data_update_every;
2159 before += this.data_update_every - (before % this.data_update_every);
2161 // the final wanted duration
2162 var wanted_duration = before - after;
2164 // to allow panning, accept just a point below our minimum
2165 if((current_duration - this.data_update_every) < min_duration)
2166 min_duration = current_duration - this.data_update_every;
2168 // we do it, but we adjust to minimum size and return false
2169 // when the wanted size is below the current and the minimum
2171 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2172 if(this.debug === true)
2173 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2175 min_duration = this.fixed_min_duration;
2177 var dt = (min_duration - wanted_duration) / 2;
2180 wanted_duration = before - after;
2184 var tolerance = this.data_update_every * 2;
2185 var movement = Math.abs(before - this.view_before);
2187 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2188 if(this.debug === true)
2189 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);
2193 if(this.current.name === 'auto') {
2194 this.log(logme + 'caller called me with mode: ' + this.current.name);
2195 this.setMode('pan');
2198 if(this.debug === true)
2199 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);
2201 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2202 this.current.force_after_ms = after;
2203 this.current.force_before_ms = before;
2204 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2208 this.legendFormatValue = function(value) {
2209 if(value === null || value === 'undefined') return '-';
2210 if(typeof value !== 'number') return value;
2212 if(this.value_decimal_detail !== -1)
2213 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2215 var abs = Math.abs(value);
2216 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2217 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2218 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2219 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2220 return (Math.round(value * 10000) / 10000).toLocaleString();
2223 this.legendSetLabelValue = function(label, value) {
2224 var series = this.element_legend_childs.series[label];
2225 if(typeof series === 'undefined') return;
2226 if(series.value === null && series.user === null) return;
2229 // this slows down firefox and edge significantly
2230 // since it requires to use innerHTML(), instead of innerText()
2232 // if the value has not changed, skip DOM update
2233 //if(series.last === value) return;
2236 if(typeof value === 'number') {
2237 var v = Math.abs(value);
2238 s = r = this.legendFormatValue(value);
2240 if(typeof series.last === 'number') {
2241 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2242 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2243 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2245 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2255 series.last = value;
2259 var s = this.legendFormatValue(value);
2261 // caching: do not update the update to show the same value again
2262 if(s === series.last_shown_value) return;
2263 series.last_shown_value = s;
2265 if(series.value !== null) series.value.innerText = s;
2266 if(series.user !== null) series.user.innerText = s;
2269 this.__legendSetDateString = function(date) {
2270 if(date !== this.__last_shown_legend_date) {
2271 this.element_legend_childs.title_date.innerText = date;
2272 this.__last_shown_legend_date = date;
2276 this.__legendSetTimeString = function(time) {
2277 if(time !== this.__last_shown_legend_time) {
2278 this.element_legend_childs.title_time.innerText = time;
2279 this.__last_shown_legend_time = time;
2283 this.__legendSetUnitsString = function(units) {
2284 if(units !== this.__last_shown_legend_units) {
2285 this.element_legend_childs.title_units.innerText = units;
2286 this.__last_shown_legend_units = units;
2290 this.legendSetDate = function(ms) {
2291 if(typeof ms !== 'number') {
2292 this.legendShowUndefined();
2296 var d = new Date(ms);
2298 if(this.element_legend_childs.title_date)
2299 this.__legendSetDateString(d.toLocaleDateString());
2301 if(this.element_legend_childs.title_time)
2302 this.__legendSetTimeString(d.toLocaleTimeString());
2304 if(this.element_legend_childs.title_units)
2305 this.__legendSetUnitsString(this.units)
2308 this.legendShowUndefined = function() {
2309 if(this.element_legend_childs.title_date)
2310 this.__legendSetDateString(' ');
2312 if(this.element_legend_childs.title_time)
2313 this.__legendSetTimeString(this.chart.name);
2315 if(this.element_legend_childs.title_units)
2316 this.__legendSetUnitsString(' ')
2318 if(this.data && this.element_legend_childs.series !== null) {
2319 var labels = this.data.dimension_names;
2320 var i = labels.length;
2322 var label = labels[i];
2324 if(typeof label === 'undefined') continue;
2325 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2326 this.legendSetLabelValue(label, null);
2331 this.legendShowLatestValues = function() {
2332 if(this.chart === null) return;
2333 if(this.selected) return;
2335 if(this.data === null || this.element_legend_childs.series === null) {
2336 this.legendShowUndefined();
2340 var show_undefined = true;
2341 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2342 show_undefined = false;
2344 if(show_undefined) {
2345 this.legendShowUndefined();
2349 this.legendSetDate(this.view_before);
2351 var labels = this.data.dimension_names;
2352 var i = labels.length;
2354 var label = labels[i];
2356 if(typeof label === 'undefined') continue;
2357 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2360 this.legendSetLabelValue(label, null);
2362 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2366 this.legendReset = function() {
2367 this.legendShowLatestValues();
2370 // this should be called just ONCE per dimension per chart
2371 this._chartDimensionColor = function(label) {
2372 if(this.colors === null) this.chartColors();
2374 if(typeof this.colors_assigned[label] === 'undefined') {
2375 if(this.colors_available.length === 0) {
2376 var len = NETDATA.themes.current.colors.length;
2378 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2381 this.colors_assigned[label] = this.colors_available.shift();
2383 if(this.debug === true)
2384 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2387 if(this.debug === true)
2388 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2391 this.colors.push(this.colors_assigned[label]);
2392 return this.colors_assigned[label];
2395 this.chartColors = function() {
2396 if(this.colors !== null) return this.colors;
2398 this.colors = new Array();
2399 this.colors_available = new Array();
2401 // add the standard colors
2402 var len = NETDATA.themes.current.colors.length;
2404 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2406 // add the user supplied colors
2407 var c = $(this.element).data('colors');
2408 // this.log('read colors: ' + c);
2409 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2410 if(typeof c !== 'string') {
2411 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2421 this.colors_available.unshift(c[len]);
2422 // this.log('adding color: ' + c[len]);
2431 this.legendUpdateDOM = function() {
2434 // check that the legend DOM is up to date for the downloaded dimensions
2435 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2436 // this.log('the legend does not have any series - requesting legend update');
2439 else if(this.data === null) {
2440 // this.log('the chart does not have any data - requesting legend update');
2443 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2447 var labels = this.data.dimension_names.toString();
2448 if(labels !== this.element_legend_childs.series.labels_key) {
2451 if(this.debug === true)
2452 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2456 if(needed === false) {
2457 // make sure colors available
2460 // do we have to update the current values?
2461 // we do this, only when the visible chart is current
2462 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2463 if(this.debug === true)
2464 this.log('chart is in latest position... updating values on legend...');
2466 //var labels = this.data.dimension_names;
2467 //var i = labels.length;
2469 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2473 if(this.colors === null) {
2474 // this is the first time we update the chart
2475 // let's assign colors to all dimensions
2476 if(this.library.track_colors() === true)
2477 for(var dim in this.chart.dimensions)
2478 this._chartDimensionColor(this.chart.dimensions[dim].name);
2480 // we will re-generate the colors for the chart
2481 // based on the selected dimensions
2484 if(this.debug === true)
2485 this.log('updating Legend DOM');
2487 // mark all dimensions as invalid
2488 this.dimensions_visibility.invalidateAll();
2490 var genLabel = function(state, parent, dim, name, count) {
2491 var color = state._chartDimensionColor(name);
2493 var user_element = null;
2494 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2495 if(user_id === null)
2496 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2497 if(user_id !== null) {
2498 user_element = document.getElementById(user_id) || null;
2499 if (user_element === null)
2500 state.log('Cannot find element with id: ' + user_id);
2503 state.element_legend_childs.series[name] = {
2504 name: document.createElement('span'),
2505 value: document.createElement('span'),
2508 last_shown_value: null
2511 var label = state.element_legend_childs.series[name];
2513 // create the dimension visibility tracking for this label
2514 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2516 var rgb = NETDATA.colorHex2Rgb(color);
2517 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2518 + state.chart.chart_type
2519 + '" style="background-color: '
2520 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2521 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2523 var text = document.createTextNode(' ' + name);
2524 label.name.appendChild(text);
2527 parent.appendChild(document.createElement('br'));
2529 parent.appendChild(label.name);
2530 parent.appendChild(label.value);
2533 var content = document.createElement('div');
2535 if(this.hasLegend()) {
2536 this.element_legend_childs = {
2538 resize_handler: document.createElement('div'),
2539 toolbox: document.createElement('div'),
2540 toolbox_left: document.createElement('div'),
2541 toolbox_right: document.createElement('div'),
2542 toolbox_reset: document.createElement('div'),
2543 toolbox_zoomin: document.createElement('div'),
2544 toolbox_zoomout: document.createElement('div'),
2545 toolbox_volume: document.createElement('div'),
2546 title_date: document.createElement('span'),
2547 title_time: document.createElement('span'),
2548 title_units: document.createElement('span'),
2549 perfect_scroller: document.createElement('div'),
2553 this.element_legend.innerHTML = '';
2555 if(this.library.toolboxPanAndZoom !== null) {
2557 function get_pan_and_zoom_step(event) {
2559 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2561 else if (event.shiftKey)
2562 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2564 else if (event.altKey)
2565 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2568 return NETDATA.options.current.pan_and_zoom_factor;
2571 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2572 this.element.appendChild(this.element_legend_childs.toolbox);
2574 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2575 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2576 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2577 this.element_legend_childs.toolbox_left.onclick = function(e) {
2580 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2581 var before = that.view_before - step;
2582 var after = that.view_after - step;
2583 if(after >= that.netdata_first)
2584 that.library.toolboxPanAndZoom(that, after, before);
2586 if(NETDATA.options.current.show_help === true)
2587 $(this.element_legend_childs.toolbox_left).popover({
2592 placement: 'bottom',
2593 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2595 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>'
2599 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2600 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2601 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2602 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2604 NETDATA.resetAllCharts(that);
2606 if(NETDATA.options.current.show_help === true)
2607 $(this.element_legend_childs.toolbox_reset).popover({
2612 placement: 'bottom',
2613 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2614 title: 'Chart Reset',
2615 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>'
2618 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2619 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2620 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2621 this.element_legend_childs.toolbox_right.onclick = function(e) {
2623 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2624 var before = that.view_before + step;
2625 var after = that.view_after + step;
2626 if(before <= that.netdata_last)
2627 that.library.toolboxPanAndZoom(that, after, before);
2629 if(NETDATA.options.current.show_help === true)
2630 $(this.element_legend_childs.toolbox_right).popover({
2635 placement: 'bottom',
2636 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2638 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>'
2642 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2643 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2644 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2645 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2647 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2648 var before = that.view_before - dt;
2649 var after = that.view_after + dt;
2650 that.library.toolboxPanAndZoom(that, after, before);
2652 if(NETDATA.options.current.show_help === true)
2653 $(this.element_legend_childs.toolbox_zoomin).popover({
2658 placement: 'bottom',
2659 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2660 title: 'Chart Zoom In',
2661 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>'
2664 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2665 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2666 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2667 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2669 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);
2670 var before = that.view_before + dt;
2671 var after = that.view_after - dt;
2673 that.library.toolboxPanAndZoom(that, after, before);
2675 if(NETDATA.options.current.show_help === true)
2676 $(this.element_legend_childs.toolbox_zoomout).popover({
2681 placement: 'bottom',
2682 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2683 title: 'Chart Zoom Out',
2684 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>'
2687 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2688 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2689 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2690 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2691 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2692 //e.preventDefault();
2693 //alert('clicked toolbox_volume on ' + that.id);
2697 this.element_legend_childs.toolbox = null;
2698 this.element_legend_childs.toolbox_left = null;
2699 this.element_legend_childs.toolbox_reset = null;
2700 this.element_legend_childs.toolbox_right = null;
2701 this.element_legend_childs.toolbox_zoomin = null;
2702 this.element_legend_childs.toolbox_zoomout = null;
2703 this.element_legend_childs.toolbox_volume = null;
2706 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2707 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2708 this.element.appendChild(this.element_legend_childs.resize_handler);
2709 if(NETDATA.options.current.show_help === true)
2710 $(this.element_legend_childs.resize_handler).popover({
2715 placement: 'bottom',
2716 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2717 title: 'Chart Resize',
2718 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>'
2722 this.element_legend_childs.resize_handler.onmousedown =
2724 that.resizeHandler(e);
2728 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2729 that.resizeHandler(e);
2732 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2733 this.element_legend.appendChild(this.element_legend_childs.title_date);
2735 this.element_legend.appendChild(document.createElement('br'));
2737 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2738 this.element_legend.appendChild(this.element_legend_childs.title_time);
2740 this.element_legend.appendChild(document.createElement('br'));
2742 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2743 this.element_legend.appendChild(this.element_legend_childs.title_units);
2745 this.element_legend.appendChild(document.createElement('br'));
2747 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2748 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2750 content.className = 'netdata-legend-series-content';
2751 this.element_legend_childs.perfect_scroller.appendChild(content);
2753 if(NETDATA.options.current.show_help === true)
2754 $(content).popover({
2759 placement: 'bottom',
2760 title: 'Chart Legend',
2761 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2762 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>'
2766 this.element_legend_childs = {
2768 resize_handler: null,
2771 toolbox_right: null,
2772 toolbox_reset: null,
2773 toolbox_zoomin: null,
2774 toolbox_zoomout: null,
2775 toolbox_volume: null,
2779 perfect_scroller: null,
2785 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2786 if(this.debug === true)
2787 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2789 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2790 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2794 var tmp = new Array();
2795 for(var dim in this.chart.dimensions) {
2796 tmp.push(this.chart.dimensions[dim].name);
2797 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2799 this.element_legend_childs.series.labels_key = tmp.toString();
2800 if(this.debug === true)
2801 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2804 // create a hidden div to be used for hidding
2805 // the original legend of the chart library
2806 var el = document.createElement('div');
2807 if(this.element_legend !== null)
2808 this.element_legend.appendChild(el);
2809 el.style.display = 'none';
2811 this.element_legend_childs.hidden = document.createElement('div');
2812 el.appendChild(this.element_legend_childs.hidden);
2814 if(this.element_legend_childs.perfect_scroller !== null) {
2815 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2817 wheelPropagation: true,
2818 swipePropagation: true,
2819 minScrollbarLength: null,
2820 maxScrollbarLength: null,
2821 useBothWheelAxes: false,
2822 suppressScrollX: true,
2823 suppressScrollY: false,
2824 scrollXMarginOffset: 0,
2825 scrollYMarginOffset: 0,
2828 Ps.update(this.element_legend_childs.perfect_scroller);
2831 this.legendShowLatestValues();
2834 this.hasLegend = function() {
2835 if(typeof this.___hasLegendCache___ !== 'undefined')
2836 return this.___hasLegendCache___;
2839 if(this.library && this.library.legend(this) === 'right-side') {
2840 var legend = $(this.element).data('legend') || 'yes';
2841 if(legend === 'yes') leg = true;
2844 this.___hasLegendCache___ = leg;
2848 this.legendWidth = function() {
2849 return (this.hasLegend())?140:0;
2852 this.legendHeight = function() {
2853 return $(this.element).height();
2856 this.chartWidth = function() {
2857 return $(this.element).width() - this.legendWidth();
2860 this.chartHeight = function() {
2861 return $(this.element).height();
2864 this.chartPixelsPerPoint = function() {
2865 // force an options provided detail
2866 var px = this.pixels_per_point;
2868 if(this.library && px < this.library.pixels_per_point(this))
2869 px = this.library.pixels_per_point(this);
2871 if(px < NETDATA.options.current.pixels_per_point)
2872 px = NETDATA.options.current.pixels_per_point;
2877 this.needsRecreation = function() {
2879 this.chart_created === true
2881 && this.library.autoresize() === false
2882 && this.tm.last_resized < NETDATA.options.last_resized
2886 this.chartURL = function() {
2887 var after, before, points_multiplier = 1;
2888 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2889 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2891 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2892 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2893 this.view_after = after * 1000;
2894 this.view_before = before * 1000;
2896 this.requested_padding = null;
2897 points_multiplier = 1;
2899 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2900 this.tm.pan_and_zoom_seq = 0;
2902 before = Math.round(this.current.force_before_ms / 1000);
2903 after = Math.round(this.current.force_after_ms / 1000);
2904 this.view_after = after * 1000;
2905 this.view_before = before * 1000;
2907 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2908 this.requested_padding = Math.round((before - after) / 2);
2909 after -= this.requested_padding;
2910 before += this.requested_padding;
2911 this.requested_padding *= 1000;
2912 points_multiplier = 2;
2915 this.current.force_before_ms = null;
2916 this.current.force_after_ms = null;
2919 this.tm.pan_and_zoom_seq = 0;
2921 before = this.before;
2923 this.view_after = after * 1000;
2924 this.view_before = before * 1000;
2926 this.requested_padding = null;
2927 points_multiplier = 1;
2930 this.requested_after = after * 1000;
2931 this.requested_before = before * 1000;
2933 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2935 // build the data URL
2936 this.data_url = this.host + this.chart.data_url;
2937 this.data_url += "&format=" + this.library.format();
2938 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2939 this.data_url += "&group=" + this.method;
2941 if(this.override_options !== null)
2942 this.data_url += "&options=" + this.override_options.toString();
2944 this.data_url += "&options=" + this.library.options(this);
2946 this.data_url += '|jsonwrap';
2948 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2949 this.data_url += '|nonzero';
2951 if(this.append_options !== null)
2952 this.data_url += '|' + this.append_options.toString();
2955 this.data_url += "&after=" + after.toString();
2958 this.data_url += "&before=" + before.toString();
2961 this.data_url += "&dimensions=" + this.dimensions;
2963 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2964 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2967 this.redrawChart = function() {
2968 if(this.data !== null)
2969 this.updateChartWithData(this.data);
2972 this.updateChartWithData = function(data) {
2973 if(this.debug === true)
2974 this.log('updateChartWithData() called.');
2976 // this may force the chart to be re-created
2980 this.updates_counter++;
2981 this.updates_since_last_unhide++;
2982 this.updates_since_last_creation++;
2984 var started = Date.now();
2986 // if the result is JSON, find the latest update-every
2987 this.data_update_every = data.view_update_every * 1000;
2988 this.data_after = data.after * 1000;
2989 this.data_before = data.before * 1000;
2990 this.netdata_first = data.first_entry * 1000;
2991 this.netdata_last = data.last_entry * 1000;
2992 this.data_points = data.points;
2995 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2996 if(this.view_after < this.data_after) {
2997 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2998 this.view_after = this.data_after;
3001 if(this.view_before > this.data_before) {
3002 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3003 this.view_before = this.data_before;
3007 this.view_after = this.data_after;
3008 this.view_before = this.data_before;
3011 if(this.debug === true) {
3012 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3014 if(this.current.force_after_ms)
3015 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3017 this.log('STATUS: forced : unset');
3019 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3020 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3021 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3022 this.log('STATUS: points : ' + (this.data_points).toString());
3025 if(this.data_points === 0) {
3030 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3031 if(this.debug === true)
3032 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3034 this.chart_created = false;
3037 // check and update the legend
3038 this.legendUpdateDOM();
3040 if(this.chart_created === true
3041 && typeof this.library.update === 'function') {
3043 if(this.debug === true)
3044 this.log('updating chart...');
3046 if(callChartLibraryUpdateSafely(data) === false)
3050 if(this.debug === true)
3051 this.log('creating chart...');
3053 if(callChartLibraryCreateSafely(data) === false)
3057 this.legendShowLatestValues();
3058 if(this.selected === true)
3059 NETDATA.globalSelectionSync.stop();
3061 // update the performance counters
3062 var now = Date.now();
3063 this.tm.last_updated = now;
3065 // don't update last_autorefreshed if this chart is
3066 // forced to be updated with global PanAndZoom
3067 if(NETDATA.globalPanAndZoom.isActive())
3068 this.tm.last_autorefreshed = 0;
3070 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3071 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3073 this.tm.last_autorefreshed = now;
3076 this.refresh_dt_ms = now - started;
3077 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3079 if(this.refresh_dt_element !== null)
3080 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3083 this.updateChart = function(callback) {
3084 if(this.debug === true)
3085 this.log('updateChart() called.');
3087 if(this._updating === true) {
3088 if(this.debug === true)
3089 this.log('I am already updating...');
3091 if(typeof callback === 'function') callback();
3095 // due to late initialization of charts and libraries
3096 // we need to check this too
3097 if(this.enabled === false) {
3098 if(this.debug === true)
3099 this.log('I am not enabled');
3101 if(typeof callback === 'function') callback();
3105 if(canBeRendered() === false) {
3106 if(typeof callback === 'function') callback();
3110 if(this.chart === null) {
3111 this.getChart(function() { that.updateChart(callback); });
3115 if(this.library.initialized === false) {
3116 if(this.library.enabled === true) {
3117 this.library.initialize(function() { that.updateChart(callback); });
3121 error('chart library "' + this.library_name + '" is not available.');
3122 if(typeof callback === 'function') callback();
3127 this.clearSelection();
3130 if(this.debug === true)
3131 this.log('updating from ' + this.data_url);
3133 NETDATA.statistics.refreshes_total++;
3134 NETDATA.statistics.refreshes_active++;
3136 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3137 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3139 this._updating = true;
3141 this.xhr = $.ajax( {
3146 'Cache-Control': 'no-cache, no-store',
3147 'Pragma': 'no-cache'
3149 xhrFields: { withCredentials: true } // required for the cookie
3151 .done(function(data) {
3152 that.xhr = undefined;
3153 that.retries_on_data_failures = 0;
3155 if(that.debug === true)
3156 that.log('data received. updating chart.');
3158 that.updateChartWithData(data);
3160 .fail(function(msg) {
3161 that.xhr = undefined;
3163 if(msg.statusText !== 'abort') {
3164 that.retries_on_data_failures++;
3165 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3166 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3167 that.retries_on_data_failures = 0;
3168 error('data download failed for url: ' + that.data_url);
3171 that.tm.last_autorefreshed = Date.now();
3172 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3176 .always(function() {
3177 that.xhr = undefined;
3179 NETDATA.statistics.refreshes_active--;
3180 that._updating = false;
3181 if(typeof callback === 'function') callback();
3187 this.isVisible = function(nocache) {
3188 if(typeof nocache === 'undefined')
3191 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3193 // caching - we do not evaluate the charts visibility
3194 // if the page has not been scrolled since the last check
3195 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3196 return this.___isVisible___;
3198 this.tm.last_visible_check = Date.now();
3200 var wh = window.innerHeight;
3201 var x = this.element.getBoundingClientRect();
3205 if(x.width === 0 || x.height === 0) {
3207 this.___isVisible___ = false;
3208 return this.___isVisible___;
3211 if(x.top < 0 && -x.top > x.height) {
3212 // the chart is entirely above
3213 ret = -x.top - x.height;
3215 else if(x.top > wh) {
3216 // the chart is entirely below
3220 if(ret > tolerance) {
3221 // the chart is too far
3224 this.___isVisible___ = false;
3225 return this.___isVisible___;
3228 // the chart is inside or very close
3231 this.___isVisible___ = true;
3232 return this.___isVisible___;
3236 this.isAutoRefreshable = function() {
3237 return (this.current.autorefresh);
3240 this.canBeAutoRefreshed = function() {
3241 var now = Date.now();
3243 if(this.running === true) {
3244 if(this.debug === true)
3245 this.log('I am already running');
3250 if(this.enabled === false) {
3251 if(this.debug === true)
3252 this.log('I am not enabled');
3257 if(this.library === null || this.library.enabled === false) {
3258 error('charting library "' + this.library_name + '" is not available');
3259 if(this.debug === true)
3260 this.log('My chart library ' + this.library_name + ' is not available');
3265 if(this.isVisible() === false) {
3266 if(NETDATA.options.debug.visibility === true || this.debug === true)
3267 this.log('I am not visible');
3272 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3273 if(this.debug === true)
3274 this.log('timed force update detected - allowing this update');
3276 this.current.force_update_at = 0;
3280 if(this.isAutoRefreshable() === true) {
3281 // allow the first update, even if the page is not visible
3282 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3283 if(NETDATA.options.debug.focus === true || this.debug === true)
3284 this.log('canBeAutoRefreshed(): page does not have focus');
3289 if(this.needsRecreation() === true) {
3290 if(this.debug === true)
3291 this.log('canBeAutoRefreshed(): needs re-creation.');
3296 // options valid only for autoRefresh()
3297 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3298 if(NETDATA.globalPanAndZoom.isActive()) {
3299 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3300 if(this.debug === true)
3301 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3306 if(this.debug === true)
3307 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3313 if(this.selected === true) {
3314 if(this.debug === true)
3315 this.log('canBeAutoRefreshed(): I have a selection in place.');
3320 if(this.paused === true) {
3321 if(this.debug === true)
3322 this.log('canBeAutoRefreshed(): I am paused.');
3327 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3328 if(this.debug === true)
3329 this.log('canBeAutoRefreshed(): It is time to update me.');
3339 this.autoRefresh = function(callback) {
3340 if(this.canBeAutoRefreshed() === true && this.running === false) {
3343 state.running = true;
3344 state.updateChart(function() {
3345 state.running = false;
3347 if(typeof callback !== 'undefined')
3352 if(typeof callback !== 'undefined')
3357 this._defaultsFromDownloadedChart = function(chart) {
3359 this.chart_url = chart.url;
3360 this.data_update_every = chart.update_every * 1000;
3361 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3362 this.tm.last_info_downloaded = Date.now();
3364 if(this.title === null)
3365 this.title = chart.title;
3367 if(this.units === null)
3368 this.units = chart.units;
3371 // fetch the chart description from the netdata server
3372 this.getChart = function(callback) {
3373 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3375 this._defaultsFromDownloadedChart(this.chart);
3376 if(typeof callback === 'function') callback();
3379 this.chart_url = "/api/v1/chart?chart=" + this.id;
3381 if(this.debug === true)
3382 this.log('downloading ' + this.chart_url);
3385 url: this.host + this.chart_url,
3388 xhrFields: { withCredentials: true } // required for the cookie
3390 .done(function(chart) {
3391 chart.url = that.chart_url;
3392 that._defaultsFromDownloadedChart(chart);
3393 NETDATA.chartRegistry.add(that.host, that.id, chart);
3396 NETDATA.error(404, that.chart_url);
3397 error('chart not found on url "' + that.chart_url + '"');
3399 .always(function() {
3400 if(typeof callback === 'function') callback();
3405 // ============================================================================================================
3411 NETDATA.resetAllCharts = function(state) {
3412 // first clear the global selection sync
3413 // to make sure no chart is in selected state
3414 state.globalSelectionSyncStop();
3416 // there are 2 possibilities here
3417 // a. state is the global Pan and Zoom master
3418 // b. state is not the global Pan and Zoom master
3420 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3423 // clear the global Pan and Zoom
3424 // this will also refresh the master
3425 // and unblock any charts currently mirroring the master
3426 NETDATA.globalPanAndZoom.clearMaster();
3428 // if we were not the master, reset our status too
3429 // this is required because most probably the mouse
3430 // is over this chart, blocking it from auto-refreshing
3431 if(master === false && (state.paused === true || state.selected === true))
3435 // get or create a chart state, given a DOM element
3436 NETDATA.chartState = function(element) {
3437 var state = $(element).data('netdata-state-object') || null;
3438 if(state === null) {
3439 state = new chartState(element);
3440 $(element).data('netdata-state-object', state);
3445 // ----------------------------------------------------------------------------------------------------------------
3446 // Library functions
3448 // Load a script without jquery
3449 // This is used to load jquery - after it is loaded, we use jquery
3450 NETDATA._loadjQuery = function(callback) {
3451 if(typeof jQuery === 'undefined') {
3452 if(NETDATA.options.debug.main_loop === true)
3453 console.log('loading ' + NETDATA.jQuery);
3455 var script = document.createElement('script');
3456 script.type = 'text/javascript';
3457 script.async = true;
3458 script.src = NETDATA.jQuery;
3460 // script.onabort = onError;
3461 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3462 if(typeof callback === "function")
3463 script.onload = callback;
3465 var s = document.getElementsByTagName('script')[0];
3466 s.parentNode.insertBefore(script, s);
3468 else if(typeof callback === "function")
3472 NETDATA._loadCSS = function(filename) {
3473 // don't use jQuery here
3474 // styles are loaded before jQuery
3475 // to eliminate showing an unstyled page to the user
3477 var fileref = document.createElement("link");
3478 fileref.setAttribute("rel", "stylesheet");
3479 fileref.setAttribute("type", "text/css");
3480 fileref.setAttribute("href", filename);
3482 if (typeof fileref !== 'undefined')
3483 document.getElementsByTagName("head")[0].appendChild(fileref);
3486 NETDATA.colorHex2Rgb = function(hex) {
3487 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3488 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3489 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3490 return r + r + g + g + b + b;
3493 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3495 r: parseInt(result[1], 16),
3496 g: parseInt(result[2], 16),
3497 b: parseInt(result[3], 16)
3501 NETDATA.colorLuminance = function(hex, lum) {
3502 // validate hex string
3503 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3505 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3509 // convert to decimal and change luminosity
3510 var rgb = "#", c, i;
3511 for (i = 0; i < 3; i++) {
3512 c = parseInt(hex.substr(i*2,2), 16);
3513 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3514 rgb += ("00"+c).substr(c.length);
3520 NETDATA.guid = function() {
3522 return Math.floor((1 + Math.random()) * 0x10000)
3527 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3530 NETDATA.zeropad = function(x) {
3531 if(x > -10 && x < 10) return '0' + x.toString();
3532 else return x.toString();
3535 // user function to signal us the DOM has been
3537 NETDATA.updatedDom = function() {
3538 NETDATA.options.updated_dom = true;
3541 NETDATA.ready = function(callback) {
3542 NETDATA.options.pauseCallback = callback;
3545 NETDATA.pause = function(callback) {
3546 if(NETDATA.options.pause === true)
3549 NETDATA.options.pauseCallback = callback;
3552 NETDATA.unpause = function() {
3553 NETDATA.options.pauseCallback = null;
3554 NETDATA.options.updated_dom = true;
3555 NETDATA.options.pause = false;
3558 // ----------------------------------------------------------------------------------------------------------------
3560 // this is purely sequencial charts refresher
3561 // it is meant to be autonomous
3562 NETDATA.chartRefresherNoParallel = function(index) {
3563 if(NETDATA.options.debug.mail_loop === true)
3564 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3566 if(NETDATA.options.updated_dom === true) {
3567 // the dom has been updated
3568 // get the dom parts again
3569 NETDATA.parseDom(NETDATA.chartRefresher);
3572 if(index >= NETDATA.options.targets.length) {
3573 if(NETDATA.options.debug.main_loop === true)
3574 console.log('waiting to restart main loop...');
3576 NETDATA.options.auto_refresher_fast_weight = 0;
3578 setTimeout(function() {
3579 NETDATA.chartRefresher();
3580 }, NETDATA.options.current.idle_between_loops);
3583 var state = NETDATA.options.targets[index];
3585 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3586 if(NETDATA.options.debug.main_loop === true)
3587 console.log('fast rendering...');
3589 state.autoRefresh(function() {
3590 NETDATA.chartRefresherNoParallel(++index);
3594 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3595 NETDATA.options.auto_refresher_fast_weight = 0;
3597 setTimeout(function() {
3598 state.autoRefresh(function() {
3599 NETDATA.chartRefresherNoParallel(++index);
3601 }, NETDATA.options.current.idle_between_charts);
3606 // this is part of the parallel refresher
3607 // its cause is to refresh sequencially all the charts
3608 // that depend on chart library initialization
3609 // it will call the parallel refresher back
3610 // as soon as it sees a chart that its chart library
3612 NETDATA.chartRefresher_uninitialized = function() {
3613 if(NETDATA.options.updated_dom === true) {
3614 // the dom has been updated
3615 // get the dom parts again
3616 NETDATA.parseDom(NETDATA.chartRefresher);
3620 if(NETDATA.options.sequencial.length === 0)
3621 NETDATA.chartRefresher();
3623 var state = NETDATA.options.sequencial.pop();
3624 if(state.library.initialized === true)
3625 NETDATA.chartRefresher();
3627 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3631 NETDATA.chartRefresherWaitTime = function() {
3632 return NETDATA.options.current.idle_parallel_loops;
3635 // the default refresher
3636 // it will create 2 sets of charts:
3637 // - the ones that can be refreshed in parallel
3638 // - the ones that depend on something else
3639 // the first set will be executed in parallel
3640 // the second will be given to NETDATA.chartRefresher_uninitialized()
3641 NETDATA.chartRefresher = function() {
3642 // console.log('auto-refresher...');
3644 if(NETDATA.options.pause === true) {
3645 // console.log('auto-refresher is paused');
3646 setTimeout(NETDATA.chartRefresher,
3647 NETDATA.chartRefresherWaitTime());
3651 if(typeof NETDATA.options.pauseCallback === 'function') {
3652 // console.log('auto-refresher is calling pauseCallback');
3653 NETDATA.options.pause = true;
3654 NETDATA.options.pauseCallback();
3655 NETDATA.chartRefresher();
3659 if(NETDATA.options.current.parallel_refresher === false) {
3660 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3661 NETDATA.chartRefresherNoParallel(0);
3665 if(NETDATA.options.updated_dom === true) {
3666 // the dom has been updated
3667 // get the dom parts again
3668 // console.log('auto-refresher is calling parseDom()');
3669 NETDATA.parseDom(NETDATA.chartRefresher);
3673 var parallel = new Array();
3674 var targets = NETDATA.options.targets;
3675 var len = targets.length;
3678 state = targets[len];
3679 if(state.isVisible() === false || state.running === true)
3682 if(state.library.initialized === false) {
3683 if(state.library.enabled === true) {
3684 state.library.initialize(NETDATA.chartRefresher);
3688 state.error('chart library "' + state.library_name + '" is not enabled.');
3692 parallel.unshift(state);
3695 if(parallel.length > 0) {
3696 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3697 // this will execute the jobs in parallel
3698 $(parallel).each(function() {
3703 // console.log('auto-refresher nothing to do');
3706 // run the next refresh iteration
3707 setTimeout(NETDATA.chartRefresher,
3708 NETDATA.chartRefresherWaitTime());
3711 NETDATA.parseDom = function(callback) {
3712 NETDATA.options.last_page_scroll = Date.now();
3713 NETDATA.options.updated_dom = false;
3715 var targets = $('div[data-netdata]'); //.filter(':visible');
3717 if(NETDATA.options.debug.main_loop === true)
3718 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3720 NETDATA.options.targets = new Array();
3721 var len = targets.length;
3723 // the initialization will take care of sizing
3724 // and the "loading..." message
3725 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3728 if(typeof callback === 'function') callback();
3731 // this is the main function - where everything starts
3732 NETDATA.start = function() {
3733 // this should be called only once
3735 NETDATA.options.page_is_visible = true;
3737 $(window).blur(function() {
3738 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3739 NETDATA.options.page_is_visible = false;
3740 if(NETDATA.options.debug.focus === true)
3741 console.log('Lost Focus!');
3745 $(window).focus(function() {
3746 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3747 NETDATA.options.page_is_visible = true;
3748 if(NETDATA.options.debug.focus === true)
3749 console.log('Focus restored!');
3753 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3754 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3755 NETDATA.options.page_is_visible = false;
3756 if(NETDATA.options.debug.focus === true)
3757 console.log('Document has no focus!');
3761 // bootstrap tab switching
3762 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3764 // bootstrap modal switching
3765 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3766 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3768 // bootstrap collapse switching
3769 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3770 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3772 NETDATA.parseDom(NETDATA.chartRefresher);
3774 // Alarms initialization
3775 setTimeout(NETDATA.alarms.init, 1000);
3777 // Registry initialization
3778 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3780 if(typeof netdataCallback === 'function')
3784 // ----------------------------------------------------------------------------------------------------------------
3787 NETDATA.peityInitialize = function(callback) {
3788 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3790 url: NETDATA.peity_js,
3793 xhrFields: { withCredentials: true } // required for the cookie
3796 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3799 NETDATA.chartLibraries.peity.enabled = false;
3800 NETDATA.error(100, NETDATA.peity_js);
3802 .always(function() {
3803 if(typeof callback === "function")
3808 NETDATA.chartLibraries.peity.enabled = false;
3809 if(typeof callback === "function")
3814 NETDATA.peityChartUpdate = function(state, data) {
3815 state.peity_instance.innerHTML = data.result;
3817 if(state.peity_options.stroke !== state.chartColors()[0]) {
3818 state.peity_options.stroke = state.chartColors()[0];
3819 if(state.chart.chart_type === 'line')
3820 state.peity_options.fill = NETDATA.themes.current.background;
3822 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3825 $(state.peity_instance).peity('line', state.peity_options);
3829 NETDATA.peityChartCreate = function(state, data) {
3830 state.peity_instance = document.createElement('div');
3831 state.element_chart.appendChild(state.peity_instance);
3833 var self = $(state.element);
3834 state.peity_options = {
3835 stroke: NETDATA.themes.current.foreground,
3836 strokeWidth: self.data('peity-strokewidth') || 1,
3837 width: state.chartWidth(),
3838 height: state.chartHeight(),
3839 fill: NETDATA.themes.current.foreground
3842 NETDATA.peityChartUpdate(state, data);
3846 // ----------------------------------------------------------------------------------------------------------------
3849 NETDATA.sparklineInitialize = function(callback) {
3850 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3852 url: NETDATA.sparkline_js,
3855 xhrFields: { withCredentials: true } // required for the cookie
3858 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3861 NETDATA.chartLibraries.sparkline.enabled = false;
3862 NETDATA.error(100, NETDATA.sparkline_js);
3864 .always(function() {
3865 if(typeof callback === "function")
3870 NETDATA.chartLibraries.sparkline.enabled = false;
3871 if(typeof callback === "function")
3876 NETDATA.sparklineChartUpdate = function(state, data) {
3877 state.sparkline_options.width = state.chartWidth();
3878 state.sparkline_options.height = state.chartHeight();
3880 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3884 NETDATA.sparklineChartCreate = function(state, data) {
3885 var self = $(state.element);
3886 var type = self.data('sparkline-type') || 'line';
3887 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3888 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3889 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3890 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3891 var composite = self.data('sparkline-composite') || undefined;
3892 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3893 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3894 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3895 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3896 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3897 var spotColor = self.data('sparkline-spotcolor') || undefined;
3898 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3899 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3900 var spotRadius = self.data('sparkline-spotradius') || undefined;
3901 var valueSpots = self.data('sparkline-valuespots') || undefined;
3902 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3903 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3904 var lineWidth = self.data('sparkline-linewidth') || undefined;
3905 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3906 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3907 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3908 var xvalues = self.data('sparkline-xvalues') || undefined;
3909 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3910 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3911 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3912 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3913 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3914 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3915 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3916 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3917 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3918 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3919 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3920 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3921 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3922 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3923 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3924 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3925 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3926 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3927 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3928 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3929 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3930 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3932 if(spotColor === 'disable') spotColor='';
3933 if(minSpotColor === 'disable') minSpotColor='';
3934 if(maxSpotColor === 'disable') maxSpotColor='';
3936 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3938 state.sparkline_options = {
3940 lineColor: lineColor,
3941 fillColor: fillColor,
3942 chartRangeMin: chartRangeMin,
3943 chartRangeMax: chartRangeMax,
3944 composite: composite,
3945 enableTagOptions: enableTagOptions,
3946 tagOptionPrefix: tagOptionPrefix,
3947 tagValuesAttribute: tagValuesAttribute,
3948 disableHiddenCheck: disableHiddenCheck,
3949 defaultPixelsPerValue: defaultPixelsPerValue,
3950 spotColor: spotColor,
3951 minSpotColor: minSpotColor,
3952 maxSpotColor: maxSpotColor,
3953 spotRadius: spotRadius,
3954 valueSpots: valueSpots,
3955 highlightSpotColor: highlightSpotColor,
3956 highlightLineColor: highlightLineColor,
3957 lineWidth: lineWidth,
3958 normalRangeMin: normalRangeMin,
3959 normalRangeMax: normalRangeMax,
3960 drawNormalOnTop: drawNormalOnTop,
3962 chartRangeClip: chartRangeClip,
3963 chartRangeMinX: chartRangeMinX,
3964 chartRangeMaxX: chartRangeMaxX,
3965 disableInteraction: disableInteraction,
3966 disableTooltips: disableTooltips,
3967 disableHighlight: disableHighlight,
3968 highlightLighten: highlightLighten,
3969 highlightColor: highlightColor,
3970 tooltipContainer: tooltipContainer,
3971 tooltipClassname: tooltipClassname,
3972 tooltipChartTitle: state.title,
3973 tooltipFormat: tooltipFormat,
3974 tooltipPrefix: tooltipPrefix,
3975 tooltipSuffix: tooltipSuffix,
3976 tooltipSkipNull: tooltipSkipNull,
3977 tooltipValueLookups: tooltipValueLookups,
3978 tooltipFormatFieldlist: tooltipFormatFieldlist,
3979 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3980 numberFormatter: numberFormatter,
3981 numberDigitGroupSep: numberDigitGroupSep,
3982 numberDecimalMark: numberDecimalMark,
3983 numberDigitGroupCount: numberDigitGroupCount,
3984 animatedZooms: animatedZooms,
3985 width: state.chartWidth(),
3986 height: state.chartHeight()
3989 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3993 // ----------------------------------------------------------------------------------------------------------------
4000 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4001 if(after < state.netdata_first)
4002 after = state.netdata_first;
4004 if(before > state.netdata_last)
4005 before = state.netdata_last;
4007 state.setMode('zoom');
4008 state.globalSelectionSyncStop();
4009 state.globalSelectionSyncDelay();
4010 state.dygraph_user_action = true;
4011 state.dygraph_force_zoom = true;
4012 state.updateChartPanOrZoom(after, before);
4013 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4016 NETDATA.dygraphSetSelection = function(state, t) {
4017 if(typeof state.dygraph_instance !== 'undefined') {
4018 var r = state.calculateRowForTime(t);
4020 state.dygraph_instance.setSelection(r);
4022 state.dygraph_instance.clearSelection();
4023 state.legendShowUndefined();
4030 NETDATA.dygraphClearSelection = function(state, t) {
4031 if(typeof state.dygraph_instance !== 'undefined') {
4032 state.dygraph_instance.clearSelection();
4037 NETDATA.dygraphSmoothInitialize = function(callback) {
4039 url: NETDATA.dygraph_smooth_js,
4042 xhrFields: { withCredentials: true } // required for the cookie
4045 NETDATA.dygraph.smooth = true;
4046 smoothPlotter.smoothing = 0.3;
4049 NETDATA.dygraph.smooth = false;
4051 .always(function() {
4052 if(typeof callback === "function")
4057 NETDATA.dygraphInitialize = function(callback) {
4058 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4060 url: NETDATA.dygraph_js,
4063 xhrFields: { withCredentials: true } // required for the cookie
4066 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4069 NETDATA.chartLibraries.dygraph.enabled = false;
4070 NETDATA.error(100, NETDATA.dygraph_js);
4072 .always(function() {
4073 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4074 NETDATA.dygraphSmoothInitialize(callback);
4075 else if(typeof callback === "function")
4080 NETDATA.chartLibraries.dygraph.enabled = false;
4081 if(typeof callback === "function")
4086 NETDATA.dygraphChartUpdate = function(state, data) {
4087 var dygraph = state.dygraph_instance;
4089 if(typeof dygraph === 'undefined')
4090 return NETDATA.dygraphChartCreate(state, data);
4092 // when the chart is not visible, and hidden
4093 // if there is a window resize, dygraph detects
4094 // its element size as 0x0.
4095 // this will make it re-appear properly
4097 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4101 file: data.result.data,
4102 colors: state.chartColors(),
4103 labels: data.result.labels,
4104 labelsDivWidth: state.chartWidth() - 70,
4105 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4108 if(state.dygraph_force_zoom === true) {
4109 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4110 state.log('dygraphChartUpdate() forced zoom update');
4112 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4113 options.isZoomedIgnoreProgrammaticZoom = true;
4114 state.dygraph_force_zoom = false;
4116 else if(state.current.name !== 'auto') {
4117 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4118 state.log('dygraphChartUpdate() loose update');
4121 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4122 state.log('dygraphChartUpdate() strict update');
4124 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4125 options.isZoomedIgnoreProgrammaticZoom = true;
4128 options.valueRange = state.dygraph_options.valueRange;
4130 var oldMax = null, oldMin = null;
4131 if(state.__commonMin !== null) {
4132 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4133 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4135 if(state.__commonMax !== null) {
4136 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4137 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4140 if(state.dygraph_smooth_eligible === true) {
4141 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4142 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4143 NETDATA.dygraphChartCreate(state, data);
4148 dygraph.updateOptions(options);
4151 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4152 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4153 options.valueRange[0] = NETDATA.commonMin.get(state);
4156 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4157 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4158 options.valueRange[1] = NETDATA.commonMax.get(state);
4162 if(redraw === true) {
4163 // state.log('forcing redraw to adapt to common- min/max');
4164 dygraph.updateOptions(options);
4167 state.dygraph_last_rendered = Date.now();
4171 NETDATA.dygraphChartCreate = function(state, data) {
4172 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4173 state.log('dygraphChartCreate()');
4175 var self = $(state.element);
4177 var chart_type = state.chart.chart_type;
4178 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4179 chart_type = self.data('dygraph-type') || chart_type;
4181 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4182 smooth = self.data('dygraph-smooth') || smooth;
4184 if(NETDATA.dygraph.smooth === false)
4187 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4188 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4190 state.dygraph_options = {
4191 colors: self.data('dygraph-colors') || state.chartColors(),
4193 // leave a few pixels empty on the right of the chart
4194 rightGap: self.data('dygraph-rightgap') || 5,
4195 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4196 showRoller: self.data('dygraph-showroller') || false,
4198 title: self.data('dygraph-title') || state.title,
4199 titleHeight: self.data('dygraph-titleheight') || 19,
4201 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4202 labels: data.result.labels,
4203 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4204 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4205 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4206 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4207 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4210 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4211 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4213 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4214 xRangePad: self.data('dygraph-xrangepad') || 0,
4215 yRangePad: self.data('dygraph-yrangepad') || 1,
4217 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4219 ylabel: state.units,
4220 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4222 // the function to plot the chart
4225 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4226 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4227 strokePattern: self.data('dygraph-strokepattern') || undefined,
4229 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4230 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4231 drawPoints: self.data('dygraph-drawpoints') || false,
4233 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4234 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4236 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4237 pointSize: self.data('dygraph-pointsize') || 1,
4239 // enabling this makes the chart with little square lines
4240 stepPlot: self.data('dygraph-stepplot') || false,
4242 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4243 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4244 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4246 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4247 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4248 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4249 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4251 drawAxis: self.data('dygraph-drawaxis') || true,
4252 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4253 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4254 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4256 drawGrid: self.data('dygraph-drawgrid') || true,
4257 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4258 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4259 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4261 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4262 sigFigs: self.data('dygraph-sigfigs') || null,
4263 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4264 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4266 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4267 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4268 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4270 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4271 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4275 ticker: Dygraph.dateTicker,
4276 axisLabelFormatter: function (d, gran) {
4277 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4279 valueFormatter: function (ms) {
4280 //var d = new Date(ms);
4281 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4283 // no need to return anything here
4290 valueFormatter: function (x) {
4291 // we format legends with the state object
4292 // no need to do anything here
4293 // return (Math.round(x*100) / 100).toLocaleString();
4294 // return state.legendFormatValue(x);
4299 legendFormatter: function(data) {
4300 var elements = state.element_legend_childs;
4302 // if the hidden div is not there
4303 // we are not managing the legend
4304 if(elements.hidden === null) return;
4306 if (typeof data.x !== 'undefined') {
4307 state.legendSetDate(data.x);
4308 var i = data.series.length;
4310 var series = data.series[i];
4311 if(series.isVisible === true)
4312 state.legendSetLabelValue(series.label, series.y);
4318 drawCallback: function(dygraph, is_initial) {
4319 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4320 state.dygraph_user_action = false;
4322 var x_range = dygraph.xAxisRange();
4323 var after = Math.round(x_range[0]);
4324 var before = Math.round(x_range[1]);
4326 if(NETDATA.options.debug.dygraph === true)
4327 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4329 if(before <= state.netdata_last && after >= state.netdata_first)
4330 state.updateChartPanOrZoom(after, before);
4333 zoomCallback: function(minDate, maxDate, yRanges) {
4334 if(NETDATA.options.debug.dygraph === true)
4335 state.log('dygraphZoomCallback()');
4337 state.globalSelectionSyncStop();
4338 state.globalSelectionSyncDelay();
4339 state.setMode('zoom');
4341 // refresh it to the greatest possible zoom level
4342 state.dygraph_user_action = true;
4343 state.dygraph_force_zoom = true;
4344 state.updateChartPanOrZoom(minDate, maxDate);
4346 highlightCallback: function(event, x, points, row, seriesName) {
4347 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4348 state.log('dygraphHighlightCallback()');
4352 // there is a bug in dygraph when the chart is zoomed enough
4353 // the time it thinks is selected is wrong
4354 // here we calculate the time t based on the row number selected
4356 var t = state.data_after + row * state.data_update_every;
4357 // 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);
4359 state.globalSelectionSync(x);
4361 // fix legend zIndex using the internal structures of dygraph legend module
4362 // this works, but it is a hack!
4363 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4365 unhighlightCallback: function(event) {
4366 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4367 state.log('dygraphUnhighlightCallback()');
4369 state.unpauseChart();
4370 state.globalSelectionSyncStop();
4372 interactionModel : {
4373 mousedown: function(event, dygraph, context) {
4374 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4375 state.log('interactionModel.mousedown()');
4377 state.dygraph_user_action = true;
4378 state.globalSelectionSyncStop();
4380 if(NETDATA.options.debug.dygraph === true)
4381 state.log('dygraphMouseDown()');
4383 // Right-click should not initiate a zoom.
4384 if(event.button && event.button === 2) return;
4386 context.initializeMouseDown(event, dygraph, context);
4388 if(event.button && event.button === 1) {
4389 if (event.altKey || event.shiftKey) {
4390 state.setMode('pan');
4391 state.globalSelectionSyncDelay();
4392 Dygraph.startPan(event, dygraph, context);
4395 state.setMode('zoom');
4396 state.globalSelectionSyncDelay();
4397 Dygraph.startZoom(event, dygraph, context);
4401 if (event.altKey || event.shiftKey) {
4402 state.setMode('zoom');
4403 state.globalSelectionSyncDelay();
4404 Dygraph.startZoom(event, dygraph, context);
4407 state.setMode('pan');
4408 state.globalSelectionSyncDelay();
4409 Dygraph.startPan(event, dygraph, context);
4413 mousemove: function(event, dygraph, context) {
4414 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4415 state.log('interactionModel.mousemove()');
4417 if(context.isPanning) {
4418 state.dygraph_user_action = true;
4419 state.globalSelectionSyncStop();
4420 state.globalSelectionSyncDelay();
4421 state.setMode('pan');
4422 context.is2DPan = false;
4423 Dygraph.movePan(event, dygraph, context);
4425 else if(context.isZooming) {
4426 state.dygraph_user_action = true;
4427 state.globalSelectionSyncStop();
4428 state.globalSelectionSyncDelay();
4429 state.setMode('zoom');
4430 Dygraph.moveZoom(event, dygraph, context);
4433 mouseup: function(event, dygraph, context) {
4434 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4435 state.log('interactionModel.mouseup()');
4437 if (context.isPanning) {
4438 state.dygraph_user_action = true;
4439 state.globalSelectionSyncDelay();
4440 Dygraph.endPan(event, dygraph, context);
4442 else if (context.isZooming) {
4443 state.dygraph_user_action = true;
4444 state.globalSelectionSyncDelay();
4445 Dygraph.endZoom(event, dygraph, context);
4448 click: function(event, dygraph, context) {
4449 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4450 state.log('interactionModel.click()');
4452 event.preventDefault();
4454 dblclick: function(event, dygraph, context) {
4455 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4456 state.log('interactionModel.dblclick()');
4457 NETDATA.resetAllCharts(state);
4459 wheel: function(event, dygraph, context) {
4460 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4461 state.log('interactionModel.wheel()');
4463 // Take the offset of a mouse event on the dygraph canvas and
4464 // convert it to a pair of percentages from the bottom left.
4465 // (Not top left, bottom is where the lower value is.)
4466 function offsetToPercentage(g, offsetX, offsetY) {
4467 // This is calculating the pixel offset of the leftmost date.
4468 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4469 var yar0 = g.yAxisRange(0);
4471 // This is calculating the pixel of the higest value. (Top pixel)
4472 var yOffset = g.toDomCoords(null, yar0[1])[1];
4474 // x y w and h are relative to the corner of the drawing area,
4475 // so that the upper corner of the drawing area is (0, 0).
4476 var x = offsetX - xOffset;
4477 var y = offsetY - yOffset;
4479 // This is computing the rightmost pixel, effectively defining the
4481 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4483 // This is computing the lowest pixel, effectively defining the height.
4484 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4486 // Percentage from the left.
4487 var xPct = w === 0 ? 0 : (x / w);
4488 // Percentage from the top.
4489 var yPct = h === 0 ? 0 : (y / h);
4491 // The (1-) part below changes it from "% distance down from the top"
4492 // to "% distance up from the bottom".
4493 return [xPct, (1-yPct)];
4496 // Adjusts [x, y] toward each other by zoomInPercentage%
4497 // Split it so the left/bottom axis gets xBias/yBias of that change and
4498 // tight/top gets (1-xBias)/(1-yBias) of that change.
4500 // If a bias is missing it splits it down the middle.
4501 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4502 xBias = xBias || 0.5;
4503 yBias = yBias || 0.5;
4505 function adjustAxis(axis, zoomInPercentage, bias) {
4506 var delta = axis[1] - axis[0];
4507 var increment = delta * zoomInPercentage;
4508 var foo = [increment * bias, increment * (1-bias)];
4510 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4513 var yAxes = g.yAxisRanges();
4515 for (var i = 0; i < yAxes.length; i++) {
4516 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4519 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4522 if(event.altKey || event.shiftKey) {
4523 state.dygraph_user_action = true;
4525 state.globalSelectionSyncStop();
4526 state.globalSelectionSyncDelay();
4528 // http://dygraphs.com/gallery/interaction-api.js
4530 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4532 normal_def = event.wheelDelta / 40;
4535 normal_def = event.deltaY * -1.2;
4537 var normal = (event.detail) ? event.detail * -1 : normal_def;
4538 var percentage = normal / 50;
4540 if (!(event.offsetX && event.offsetY)){
4541 event.offsetX = event.layerX - event.target.offsetLeft;
4542 event.offsetY = event.layerY - event.target.offsetTop;
4545 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4546 var xPct = percentages[0];
4547 var yPct = percentages[1];
4549 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4550 var after = new_x_range[0];
4551 var before = new_x_range[1];
4553 var first = state.netdata_first + state.data_update_every;
4554 var last = state.netdata_last + state.data_update_every;
4557 after -= (before - last);
4564 state.setMode('zoom');
4565 if(state.updateChartPanOrZoom(after, before) === true)
4566 dygraph.updateOptions({ dateWindow: [ after, before ] });
4568 event.preventDefault();
4571 touchstart: function(event, dygraph, context) {
4572 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4573 state.log('interactionModel.touchstart()');
4575 state.dygraph_user_action = true;
4576 state.setMode('zoom');
4579 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4581 // we overwrite the touch directions at the end, to overwrite
4582 // the internal default of dygraphs
4583 context.touchDirections = { x: true, y: false };
4585 state.dygraph_last_touch_start = Date.now();
4586 state.dygraph_last_touch_move = 0;
4588 if(typeof event.touches[0].pageX === 'number')
4589 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4591 state.dygraph_last_touch_page_x = 0;
4593 touchmove: function(event, dygraph, context) {
4594 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4595 state.log('interactionModel.touchmove()');
4597 state.dygraph_user_action = true;
4598 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4600 state.dygraph_last_touch_move = Date.now();
4602 touchend: function(event, dygraph, context) {
4603 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4604 state.log('interactionModel.touchend()');
4606 state.dygraph_user_action = true;
4607 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4609 // if it didn't move, it is a selection
4610 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4611 // internal api of dygraphs
4612 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4613 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4614 if(NETDATA.dygraphSetSelection(state, t) === true)
4615 state.globalSelectionSync(t);
4618 // if it was double tap within double click time, reset the charts
4619 var now = Date.now();
4620 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4621 if(state.dygraph_last_touch_move === 0) {
4622 var dt = now - state.dygraph_last_touch_end;
4623 if(dt <= NETDATA.options.current.double_click_speed)
4624 NETDATA.resetAllCharts(state);
4628 // remember the timestamp of the last touch end
4629 state.dygraph_last_touch_end = now;
4634 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4635 state.dygraph_options.drawGrid = false;
4636 state.dygraph_options.drawAxis = false;
4637 state.dygraph_options.title = undefined;
4638 state.dygraph_options.ylabel = undefined;
4639 state.dygraph_options.yLabelWidth = 0;
4640 state.dygraph_options.labelsDivWidth = 120;
4641 state.dygraph_options.labelsDivStyles.width = '120px';
4642 state.dygraph_options.labelsSeparateLines = true;
4643 state.dygraph_options.rightGap = 0;
4644 state.dygraph_options.yRangePad = 1;
4647 if(smooth === true) {
4648 state.dygraph_smooth_eligible = true;
4650 if(NETDATA.options.current.smooth_plot === true)
4651 state.dygraph_options.plotter = smoothPlotter;
4653 else state.dygraph_smooth_eligible = false;
4655 state.dygraph_instance = new Dygraph(state.element_chart,
4656 data.result.data, state.dygraph_options);
4658 state.dygraph_force_zoom = false;
4659 state.dygraph_user_action = false;
4660 state.dygraph_last_rendered = Date.now();
4662 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4663 state.__commonMin = self.data('common-min') || null;
4664 state.__commonMax = self.data('common-max') || null;
4667 state.log('incompatible version of dygraphs detected');
4668 state.__commonMin = null;
4669 state.__commonMax = null;
4675 // ----------------------------------------------------------------------------------------------------------------
4678 NETDATA.morrisInitialize = function(callback) {
4679 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4681 // morris requires raphael
4682 if(!NETDATA.chartLibraries.raphael.initialized) {
4683 if(NETDATA.chartLibraries.raphael.enabled) {
4684 NETDATA.raphaelInitialize(function() {
4685 NETDATA.morrisInitialize(callback);
4689 NETDATA.chartLibraries.morris.enabled = false;
4690 if(typeof callback === "function")
4695 NETDATA._loadCSS(NETDATA.morris_css);
4698 url: NETDATA.morris_js,
4701 xhrFields: { withCredentials: true } // required for the cookie
4704 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4707 NETDATA.chartLibraries.morris.enabled = false;
4708 NETDATA.error(100, NETDATA.morris_js);
4710 .always(function() {
4711 if(typeof callback === "function")
4717 NETDATA.chartLibraries.morris.enabled = false;
4718 if(typeof callback === "function")
4723 NETDATA.morrisChartUpdate = function(state, data) {
4724 state.morris_instance.setData(data.result.data);
4728 NETDATA.morrisChartCreate = function(state, data) {
4730 state.morris_options = {
4731 element: state.element_chart.id,
4732 data: data.result.data,
4734 ykeys: data.dimension_names,
4735 labels: data.dimension_names,
4741 continuousLine: false,
4742 behaveLikeLine: false
4745 if(state.chart.chart_type === 'line')
4746 state.morris_instance = new Morris.Line(state.morris_options);
4748 else if(state.chart.chart_type === 'area') {
4749 state.morris_options.behaveLikeLine = true;
4750 state.morris_instance = new Morris.Area(state.morris_options);
4753 state.morris_instance = new Morris.Area(state.morris_options);
4758 // ----------------------------------------------------------------------------------------------------------------
4761 NETDATA.raphaelInitialize = function(callback) {
4762 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4764 url: NETDATA.raphael_js,
4767 xhrFields: { withCredentials: true } // required for the cookie
4770 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4773 NETDATA.chartLibraries.raphael.enabled = false;
4774 NETDATA.error(100, NETDATA.raphael_js);
4776 .always(function() {
4777 if(typeof callback === "function")
4782 NETDATA.chartLibraries.raphael.enabled = false;
4783 if(typeof callback === "function")
4788 NETDATA.raphaelChartUpdate = function(state, data) {
4789 $(state.element_chart).raphael(data.result, {
4790 width: state.chartWidth(),
4791 height: state.chartHeight()
4797 NETDATA.raphaelChartCreate = function(state, data) {
4798 $(state.element_chart).raphael(data.result, {
4799 width: state.chartWidth(),
4800 height: state.chartHeight()
4806 // ----------------------------------------------------------------------------------------------------------------
4809 NETDATA.c3Initialize = function(callback) {
4810 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4813 if(!NETDATA.chartLibraries.d3.initialized) {
4814 if(NETDATA.chartLibraries.d3.enabled) {
4815 NETDATA.d3Initialize(function() {
4816 NETDATA.c3Initialize(callback);
4820 NETDATA.chartLibraries.c3.enabled = false;
4821 if(typeof callback === "function")
4826 NETDATA._loadCSS(NETDATA.c3_css);
4832 xhrFields: { withCredentials: true } // required for the cookie
4835 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4838 NETDATA.chartLibraries.c3.enabled = false;
4839 NETDATA.error(100, NETDATA.c3_js);
4841 .always(function() {
4842 if(typeof callback === "function")
4848 NETDATA.chartLibraries.c3.enabled = false;
4849 if(typeof callback === "function")
4854 NETDATA.c3ChartUpdate = function(state, data) {
4855 state.c3_instance.destroy();
4856 return NETDATA.c3ChartCreate(state, data);
4858 //state.c3_instance.load({
4859 // rows: data.result,
4866 NETDATA.c3ChartCreate = function(state, data) {
4868 state.element_chart.id = 'c3-' + state.uuid;
4869 // console.log('id = ' + state.element_chart.id);
4871 state.c3_instance = c3.generate({
4872 bindto: '#' + state.element_chart.id,
4874 width: state.chartWidth(),
4875 height: state.chartHeight()
4878 pattern: state.chartColors()
4883 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4889 format: function(x) {
4890 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4917 // console.log(state.c3_instance);
4922 // ----------------------------------------------------------------------------------------------------------------
4925 NETDATA.d3Initialize = function(callback) {
4926 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4931 xhrFields: { withCredentials: true } // required for the cookie
4934 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4937 NETDATA.chartLibraries.d3.enabled = false;
4938 NETDATA.error(100, NETDATA.d3_js);
4940 .always(function() {
4941 if(typeof callback === "function")
4946 NETDATA.chartLibraries.d3.enabled = false;
4947 if(typeof callback === "function")
4952 NETDATA.d3ChartUpdate = function(state, data) {
4956 NETDATA.d3ChartCreate = function(state, data) {
4960 // ----------------------------------------------------------------------------------------------------------------
4963 NETDATA.googleInitialize = function(callback) {
4964 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4966 url: NETDATA.google_js,
4969 xhrFields: { withCredentials: true } // required for the cookie
4972 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4973 google.load('visualization', '1.1', {
4974 'packages': ['corechart', 'controls'],
4975 'callback': callback
4979 NETDATA.chartLibraries.google.enabled = false;
4980 NETDATA.error(100, NETDATA.google_js);
4981 if(typeof callback === "function")
4986 NETDATA.chartLibraries.google.enabled = false;
4987 if(typeof callback === "function")
4992 NETDATA.googleChartUpdate = function(state, data) {
4993 var datatable = new google.visualization.DataTable(data.result);
4994 state.google_instance.draw(datatable, state.google_options);
4998 NETDATA.googleChartCreate = function(state, data) {
4999 var datatable = new google.visualization.DataTable(data.result);
5001 state.google_options = {
5002 colors: state.chartColors(),
5004 // do not set width, height - the chart resizes itself
5005 //width: state.chartWidth(),
5006 //height: state.chartHeight(),
5011 // title: "Time of Day",
5012 // format:'HH:mm:ss',
5013 viewWindowMode: 'maximized',
5025 viewWindowMode: 'pretty',
5040 focusTarget: 'category',
5047 titlePosition: 'out',
5058 curveType: 'function',
5063 switch(state.chart.chart_type) {
5065 state.google_options.vAxis.viewWindowMode = 'maximized';
5066 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5067 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5071 state.google_options.isStacked = true;
5072 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5073 state.google_options.vAxis.viewWindowMode = 'maximized';
5074 state.google_options.vAxis.minValue = null;
5075 state.google_options.vAxis.maxValue = null;
5076 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5081 state.google_options.lineWidth = 2;
5082 state.google_instance = new google.visualization.LineChart(state.element_chart);
5086 state.google_instance.draw(datatable, state.google_options);
5090 // ----------------------------------------------------------------------------------------------------------------
5092 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5093 if(typeof value !== 'number') value = 0;
5094 if(typeof min !== 'number') min = 0;
5095 if(typeof max !== 'number') max = 0;
5097 if(min > value) min = value;
5098 if(max < value) max = value;
5100 // make sure it is zero based
5101 if(min > 0) min = 0;
5102 if(max < 0) max = 0;
5107 pcent = Math.round(value * 100 / max);
5108 if(pcent === 0) pcent = 0.1;
5112 pcent = Math.round(-value * 100 / min);
5113 if(pcent === 0) pcent = -0.1;
5119 // ----------------------------------------------------------------------------------------------------------------
5122 NETDATA.easypiechartInitialize = function(callback) {
5123 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5125 url: NETDATA.easypiechart_js,
5128 xhrFields: { withCredentials: true } // required for the cookie
5131 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5134 NETDATA.chartLibraries.easypiechart.enabled = false;
5135 NETDATA.error(100, NETDATA.easypiechart_js);
5137 .always(function() {
5138 if(typeof callback === "function")
5143 NETDATA.chartLibraries.easypiechart.enabled = false;
5144 if(typeof callback === "function")
5149 NETDATA.easypiechartClearSelection = function(state) {
5150 if(typeof state.easyPieChartEvent !== 'undefined') {
5151 if(state.easyPieChartEvent.timer !== null)
5152 clearTimeout(state.easyPieChartEvent.timer);
5154 state.easyPieChartEvent.timer = null;
5157 if(state.isAutoRefreshable() === true && state.data !== null) {
5158 NETDATA.easypiechartChartUpdate(state, state.data);
5161 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5162 state.easyPieChart_instance.update(0);
5164 state.easyPieChart_instance.enableAnimation();
5169 NETDATA.easypiechartSetSelection = function(state, t) {
5170 if(state.timeIsVisible(t) !== true)
5171 return NETDATA.easypiechartClearSelection(state);
5173 var slot = state.calculateRowForTime(t);
5174 if(slot < 0 || slot >= state.data.result.length)
5175 return NETDATA.easypiechartClearSelection(state);
5177 if(typeof state.easyPieChartEvent === 'undefined') {
5178 state.easyPieChartEvent = {
5185 var value = state.data.result[state.data.result.length - 1 - slot];
5186 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5187 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5188 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5190 state.easyPieChartEvent.value = value;
5191 state.easyPieChartEvent.pcent = pcent;
5192 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5194 if(state.easyPieChartEvent.timer === null) {
5195 state.easyPieChart_instance.disableAnimation();
5197 state.easyPieChartEvent.timer = setTimeout(function() {
5198 state.easyPieChartEvent.timer = null;
5199 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5200 }, NETDATA.options.current.charts_selection_animation_delay);
5206 NETDATA.easypiechartChartUpdate = function(state, data) {
5207 var value, min, max, pcent;
5209 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5214 value = data.result[0];
5215 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5216 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5217 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5220 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5221 state.easyPieChart_instance.update(pcent);
5225 NETDATA.easypiechartChartCreate = function(state, data) {
5226 var self = $(state.element);
5227 var chart = $(state.element_chart);
5229 var value = data.result[0];
5230 var min = self.data('easypiechart-min-value') || null;
5231 var max = self.data('easypiechart-max-value') || null;
5232 var adjust = self.data('easypiechart-adjust') || null;
5235 min = NETDATA.commonMin.get(state);
5236 state.easyPieChartMin = null;
5239 state.easyPieChartMin = min;
5242 max = NETDATA.commonMax.get(state);
5243 state.easyPieChartMax = null;
5246 state.easyPieChartMax = max;
5248 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5250 chart.data('data-percent', pcent);
5254 case 'width': size = state.chartHeight(); break;
5255 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5256 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5258 default: size = state.chartWidth(); break;
5260 state.element.style.width = size + 'px';
5261 state.element.style.height = size + 'px';
5263 var stroke = Math.floor(size / 22);
5264 if(stroke < 3) stroke = 2;
5266 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5267 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5268 state.easyPieChartLabel = document.createElement('span');
5269 state.easyPieChartLabel.className = 'easyPieChartLabel';
5270 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5271 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5272 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5273 state.element_chart.appendChild(state.easyPieChartLabel);
5275 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5276 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5277 state.easyPieChartTitle = document.createElement('span');
5278 state.easyPieChartTitle.className = 'easyPieChartTitle';
5279 state.easyPieChartTitle.innerText = state.title;
5280 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5281 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5282 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5283 state.element_chart.appendChild(state.easyPieChartTitle);
5285 var unitfontsize = Math.round(titlefontsize * 0.9);
5286 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5287 state.easyPieChartUnits = document.createElement('span');
5288 state.easyPieChartUnits.className = 'easyPieChartUnits';
5289 state.easyPieChartUnits.innerText = state.units;
5290 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5291 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5292 state.element_chart.appendChild(state.easyPieChartUnits);
5294 var barColor = self.data('easypiechart-barcolor');
5295 if(typeof barColor === 'undefined' || barColor === null)
5296 barColor = state.chartColors()[0];
5298 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5299 var tmp = eval(barColor);
5300 if(typeof tmp === 'function')
5304 chart.easyPieChart({
5306 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5307 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5308 scaleLength: self.data('easypiechart-scalelength') || 5,
5309 lineCap: self.data('easypiechart-linecap') || 'round',
5310 lineWidth: self.data('easypiechart-linewidth') || stroke,
5311 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5312 size: self.data('easypiechart-size') || size,
5313 rotate: self.data('easypiechart-rotate') || 0,
5314 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5315 easing: self.data('easypiechart-easing') || undefined
5318 // when we just re-create the chart
5319 // do not animate the first update
5321 if(typeof state.easyPieChart_instance !== 'undefined')
5324 state.easyPieChart_instance = chart.data('easyPieChart');
5325 if(animate === false) state.easyPieChart_instance.disableAnimation();
5326 state.easyPieChart_instance.update(pcent);
5327 if(animate === false) state.easyPieChart_instance.enableAnimation();
5331 // ----------------------------------------------------------------------------------------------------------------
5334 NETDATA.gaugeInitialize = function(callback) {
5335 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5337 url: NETDATA.gauge_js,
5340 xhrFields: { withCredentials: true } // required for the cookie
5343 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5346 NETDATA.chartLibraries.gauge.enabled = false;
5347 NETDATA.error(100, NETDATA.gauge_js);
5349 .always(function() {
5350 if(typeof callback === "function")
5355 NETDATA.chartLibraries.gauge.enabled = false;
5356 if(typeof callback === "function")
5361 NETDATA.gaugeAnimation = function(state, status) {
5364 if(typeof status === 'boolean' && status === false)
5366 else if(typeof status === 'number')
5369 // console.log('gauge speed ' + speed);
5370 state.gauge_instance.animationSpeed = speed;
5371 state.___gaugeOld__.speed = speed;
5374 NETDATA.gaugeSet = function(state, value, min, max) {
5375 if(typeof value !== 'number') value = 0;
5376 if(typeof min !== 'number') min = 0;
5377 if(typeof max !== 'number') max = 0;
5378 if(value > max) max = value;
5379 if(value < min) min = value;
5388 // gauge.js has an issue if the needle
5389 // is smaller than min or larger than max
5390 // when we set the new values
5391 // the needle will go crazy
5393 // to prevent it, we always feed it
5394 // with a percentage, so that the needle
5395 // is always between min and max
5396 var pcent = (value - min) * 100 / (max - min);
5398 // these should never happen
5399 if(pcent < 0) pcent = 0;
5400 if(pcent > 100) pcent = 100;
5402 state.gauge_instance.set(pcent);
5403 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5405 state.___gaugeOld__.value = value;
5406 state.___gaugeOld__.min = min;
5407 state.___gaugeOld__.max = max;
5410 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5411 if(state.___gaugeOld__.valueLabel !== value) {
5412 state.___gaugeOld__.valueLabel = value;
5413 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5415 if(state.___gaugeOld__.minLabel !== min) {
5416 state.___gaugeOld__.minLabel = min;
5417 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5419 if(state.___gaugeOld__.maxLabel !== max) {
5420 state.___gaugeOld__.maxLabel = max;
5421 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5425 NETDATA.gaugeClearSelection = function(state) {
5426 if(typeof state.gaugeEvent !== 'undefined') {
5427 if(state.gaugeEvent.timer !== null)
5428 clearTimeout(state.gaugeEvent.timer);
5430 state.gaugeEvent.timer = null;
5433 if(state.isAutoRefreshable() === true && state.data !== null) {
5434 NETDATA.gaugeChartUpdate(state, state.data);
5437 NETDATA.gaugeAnimation(state, false);
5438 NETDATA.gaugeSet(state, null, null, null);
5439 NETDATA.gaugeSetLabels(state, null, null, null);
5442 NETDATA.gaugeAnimation(state, true);
5446 NETDATA.gaugeSetSelection = function(state, t) {
5447 if(state.timeIsVisible(t) !== true)
5448 return NETDATA.gaugeClearSelection(state);
5450 var slot = state.calculateRowForTime(t);
5451 if(slot < 0 || slot >= state.data.result.length)
5452 return NETDATA.gaugeClearSelection(state);
5454 if(typeof state.gaugeEvent === 'undefined') {
5455 state.gaugeEvent = {
5463 var value = state.data.result[state.data.result.length - 1 - slot];
5464 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5465 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5467 // make sure it is zero based
5468 if(min > 0) min = 0;
5469 if(max < 0) max = 0;
5471 state.gaugeEvent.value = value;
5472 state.gaugeEvent.min = min;
5473 state.gaugeEvent.max = max;
5474 NETDATA.gaugeSetLabels(state, value, min, max);
5476 if(state.gaugeEvent.timer === null) {
5477 NETDATA.gaugeAnimation(state, false);
5479 state.gaugeEvent.timer = setTimeout(function() {
5480 state.gaugeEvent.timer = null;
5481 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5482 }, NETDATA.options.current.charts_selection_animation_delay);
5488 NETDATA.gaugeChartUpdate = function(state, data) {
5489 var value, min, max;
5491 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5495 NETDATA.gaugeSetLabels(state, null, null, null);
5498 value = data.result[0];
5499 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5500 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5501 if(value < min) min = value;
5502 if(value > max) max = value;
5504 // make sure it is zero based
5505 if(min > 0) min = 0;
5506 if(max < 0) max = 0;
5508 NETDATA.gaugeSetLabels(state, value, min, max);
5511 NETDATA.gaugeSet(state, value, min, max);
5515 NETDATA.gaugeChartCreate = function(state, data) {
5516 var self = $(state.element);
5517 // var chart = $(state.element_chart);
5519 var value = data.result[0];
5520 var min = self.data('gauge-min-value') || null;
5521 var max = self.data('gauge-max-value') || null;
5522 var adjust = self.data('gauge-adjust') || null;
5523 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5524 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5525 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5526 var stopColor = self.data('gauge-stop-color') || void 0;
5527 var generateGradient = self.data('gauge-generate-gradient') || false;
5530 min = NETDATA.commonMin.get(state);
5531 state.gaugeMin = null;
5534 state.gaugeMin = min;
5537 max = NETDATA.commonMax.get(state);
5538 state.gaugeMax = null;
5541 state.gaugeMax = max;
5543 // make sure it is zero based
5544 if(min > 0) min = 0;
5545 if(max < 0) max = 0;
5547 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5549 // case 'width': width = height * ratio; break;
5551 // default: height = width / ratio; break;
5553 //state.element.style.width = width.toString() + 'px';
5554 //state.element.style.height = height.toString() + 'px';
5559 lines: 12, // The number of lines to draw
5560 angle: 0.15, // The length of each line
5561 lineWidth: 0.44, // 0.44 The line thickness
5563 length: 0.8, // 0.9 The radius of the inner circle
5564 strokeWidth: 0.035, // The rotation offset
5565 color: pointerColor // Fill color
5567 colorStart: startColor, // Colors
5568 colorStop: stopColor, // just experiment with them
5569 strokeColor: strokeColor, // to see which ones work best for you
5571 generateGradient: (generateGradient === true)?true:false,
5575 if (generateGradient.constructor === Array) {
5577 // data-gauge-generate-gradient="[0, 50, 100]"
5578 // data-gauge-gradient-percent-color-0="#FFFFFF"
5579 // data-gauge-gradient-percent-color-50="#999900"
5580 // data-gauge-gradient-percent-color-100="#000000"
5582 options.percentColors = new Array();
5583 var len = generateGradient.length;
5585 var pcent = generateGradient[len];
5586 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5587 if(color !== false) {
5588 var a = new Array();
5591 options.percentColors.unshift(a);
5594 if(options.percentColors.length === 0)
5595 delete options.percentColors;
5597 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5598 options.percentColors = [
5599 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5600 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5601 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5602 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5603 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5604 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5605 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5606 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5607 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5608 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5609 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5612 state.gauge_canvas = document.createElement('canvas');
5613 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5614 state.gauge_canvas.className = 'gaugeChart';
5615 state.gauge_canvas.width = width;
5616 state.gauge_canvas.height = height;
5617 state.element_chart.appendChild(state.gauge_canvas);
5619 var valuefontsize = Math.floor(height / 6);
5620 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5621 state.gaugeChartLabel = document.createElement('span');
5622 state.gaugeChartLabel.className = 'gaugeChartLabel';
5623 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5624 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5625 state.element_chart.appendChild(state.gaugeChartLabel);
5627 var titlefontsize = Math.round(valuefontsize / 2);
5629 state.gaugeChartTitle = document.createElement('span');
5630 state.gaugeChartTitle.className = 'gaugeChartTitle';
5631 state.gaugeChartTitle.innerText = state.title;
5632 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5633 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5634 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5635 state.element_chart.appendChild(state.gaugeChartTitle);
5637 var unitfontsize = Math.round(titlefontsize * 0.9);
5638 state.gaugeChartUnits = document.createElement('span');
5639 state.gaugeChartUnits.className = 'gaugeChartUnits';
5640 state.gaugeChartUnits.innerText = state.units;
5641 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5642 state.element_chart.appendChild(state.gaugeChartUnits);
5644 state.gaugeChartMin = document.createElement('span');
5645 state.gaugeChartMin.className = 'gaugeChartMin';
5646 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5647 state.element_chart.appendChild(state.gaugeChartMin);
5649 state.gaugeChartMax = document.createElement('span');
5650 state.gaugeChartMax.className = 'gaugeChartMax';
5651 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5652 state.element_chart.appendChild(state.gaugeChartMax);
5654 // when we just re-create the chart
5655 // do not animate the first update
5657 if(typeof state.gauge_instance !== 'undefined')
5660 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5662 state.___gaugeOld__ = {
5671 // we will always feed a percentage
5672 state.gauge_instance.minValue = 0;
5673 state.gauge_instance.maxValue = 100;
5675 NETDATA.gaugeAnimation(state, animate);
5676 NETDATA.gaugeSet(state, value, min, max);
5677 NETDATA.gaugeSetLabels(state, value, min, max);
5678 NETDATA.gaugeAnimation(state, true);
5682 // ----------------------------------------------------------------------------------------------------------------
5683 // Charts Libraries Registration
5685 NETDATA.chartLibraries = {
5687 initialize: NETDATA.dygraphInitialize,
5688 create: NETDATA.dygraphChartCreate,
5689 update: NETDATA.dygraphChartUpdate,
5690 resize: function(state) {
5691 if(typeof state.dygraph_instance.resize === 'function')
5692 state.dygraph_instance.resize();
5694 setSelection: NETDATA.dygraphSetSelection,
5695 clearSelection: NETDATA.dygraphClearSelection,
5696 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5699 format: function(state) { return 'json'; },
5700 options: function(state) { return 'ms|flip'; },
5701 legend: function(state) {
5702 if(this.isSparkline(state) === false)
5703 return 'right-side';
5707 autoresize: function(state) { return true; },
5708 max_updates_to_recreate: function(state) { return 5000; },
5709 track_colors: function(state) { return true; },
5710 pixels_per_point: function(state) {
5711 if(this.isSparkline(state) === false)
5717 isSparkline: function(state) {
5718 if(typeof state.dygraph_sparkline === 'undefined') {
5719 var t = $(state.element).data('dygraph-theme');
5720 if(t === 'sparkline')
5721 state.dygraph_sparkline = true;
5723 state.dygraph_sparkline = false;
5725 return state.dygraph_sparkline;
5729 initialize: NETDATA.sparklineInitialize,
5730 create: NETDATA.sparklineChartCreate,
5731 update: NETDATA.sparklineChartUpdate,
5733 setSelection: undefined, // function(state, t) { return true; },
5734 clearSelection: undefined, // function(state) { return true; },
5735 toolboxPanAndZoom: null,
5738 format: function(state) { return 'array'; },
5739 options: function(state) { return 'flip|abs'; },
5740 legend: function(state) { return null; },
5741 autoresize: function(state) { return false; },
5742 max_updates_to_recreate: function(state) { return 5000; },
5743 track_colors: function(state) { return false; },
5744 pixels_per_point: function(state) { return 3; }
5747 initialize: NETDATA.peityInitialize,
5748 create: NETDATA.peityChartCreate,
5749 update: NETDATA.peityChartUpdate,
5751 setSelection: undefined, // function(state, t) { return true; },
5752 clearSelection: undefined, // function(state) { return true; },
5753 toolboxPanAndZoom: null,
5756 format: function(state) { return 'ssvcomma'; },
5757 options: function(state) { return 'null2zero|flip|abs'; },
5758 legend: function(state) { return null; },
5759 autoresize: function(state) { return false; },
5760 max_updates_to_recreate: function(state) { return 5000; },
5761 track_colors: function(state) { return false; },
5762 pixels_per_point: function(state) { return 3; }
5765 initialize: NETDATA.morrisInitialize,
5766 create: NETDATA.morrisChartCreate,
5767 update: NETDATA.morrisChartUpdate,
5769 setSelection: undefined, // function(state, t) { return true; },
5770 clearSelection: undefined, // function(state) { return true; },
5771 toolboxPanAndZoom: null,
5774 format: function(state) { return 'json'; },
5775 options: function(state) { return 'objectrows|ms'; },
5776 legend: function(state) { return null; },
5777 autoresize: function(state) { return false; },
5778 max_updates_to_recreate: function(state) { return 50; },
5779 track_colors: function(state) { return false; },
5780 pixels_per_point: function(state) { return 15; }
5783 initialize: NETDATA.googleInitialize,
5784 create: NETDATA.googleChartCreate,
5785 update: NETDATA.googleChartUpdate,
5787 setSelection: undefined, //function(state, t) { return true; },
5788 clearSelection: undefined, //function(state) { return true; },
5789 toolboxPanAndZoom: null,
5792 format: function(state) { return 'datatable'; },
5793 options: function(state) { return ''; },
5794 legend: function(state) { return null; },
5795 autoresize: function(state) { return false; },
5796 max_updates_to_recreate: function(state) { return 300; },
5797 track_colors: function(state) { return false; },
5798 pixels_per_point: function(state) { return 4; }
5801 initialize: NETDATA.raphaelInitialize,
5802 create: NETDATA.raphaelChartCreate,
5803 update: NETDATA.raphaelChartUpdate,
5805 setSelection: undefined, // function(state, t) { return true; },
5806 clearSelection: undefined, // function(state) { return true; },
5807 toolboxPanAndZoom: null,
5810 format: function(state) { return 'json'; },
5811 options: function(state) { return ''; },
5812 legend: function(state) { return null; },
5813 autoresize: function(state) { return false; },
5814 max_updates_to_recreate: function(state) { return 5000; },
5815 track_colors: function(state) { return false; },
5816 pixels_per_point: function(state) { return 3; }
5819 initialize: NETDATA.c3Initialize,
5820 create: NETDATA.c3ChartCreate,
5821 update: NETDATA.c3ChartUpdate,
5823 setSelection: undefined, // function(state, t) { return true; },
5824 clearSelection: undefined, // function(state) { return true; },
5825 toolboxPanAndZoom: null,
5828 format: function(state) { return 'csvjsonarray'; },
5829 options: function(state) { return 'milliseconds'; },
5830 legend: function(state) { return null; },
5831 autoresize: function(state) { return false; },
5832 max_updates_to_recreate: function(state) { return 5000; },
5833 track_colors: function(state) { return false; },
5834 pixels_per_point: function(state) { return 15; }
5837 initialize: NETDATA.d3Initialize,
5838 create: NETDATA.d3ChartCreate,
5839 update: NETDATA.d3ChartUpdate,
5841 setSelection: undefined, // function(state, t) { return true; },
5842 clearSelection: undefined, // function(state) { return true; },
5843 toolboxPanAndZoom: null,
5846 format: function(state) { return 'json'; },
5847 options: function(state) { return ''; },
5848 legend: function(state) { return null; },
5849 autoresize: function(state) { return false; },
5850 max_updates_to_recreate: function(state) { return 5000; },
5851 track_colors: function(state) { return false; },
5852 pixels_per_point: function(state) { return 3; }
5855 initialize: NETDATA.easypiechartInitialize,
5856 create: NETDATA.easypiechartChartCreate,
5857 update: NETDATA.easypiechartChartUpdate,
5859 setSelection: NETDATA.easypiechartSetSelection,
5860 clearSelection: NETDATA.easypiechartClearSelection,
5861 toolboxPanAndZoom: null,
5864 format: function(state) { return 'array'; },
5865 options: function(state) { return 'absolute'; },
5866 legend: function(state) { return null; },
5867 autoresize: function(state) { return false; },
5868 max_updates_to_recreate: function(state) { return 5000; },
5869 track_colors: function(state) { return true; },
5870 pixels_per_point: function(state) { return 3; },
5874 initialize: NETDATA.gaugeInitialize,
5875 create: NETDATA.gaugeChartCreate,
5876 update: NETDATA.gaugeChartUpdate,
5878 setSelection: NETDATA.gaugeSetSelection,
5879 clearSelection: NETDATA.gaugeClearSelection,
5880 toolboxPanAndZoom: null,
5883 format: function(state) { return 'array'; },
5884 options: function(state) { return 'absolute'; },
5885 legend: function(state) { return null; },
5886 autoresize: function(state) { return false; },
5887 max_updates_to_recreate: function(state) { return 5000; },
5888 track_colors: function(state) { return true; },
5889 pixels_per_point: function(state) { return 3; },
5894 NETDATA.registerChartLibrary = function(library, url) {
5895 if(NETDATA.options.debug.libraries === true)
5896 console.log("registering chart library: " + library);
5898 NETDATA.chartLibraries[library].url = url;
5899 NETDATA.chartLibraries[library].initialized = true;
5900 NETDATA.chartLibraries[library].enabled = true;
5903 // ----------------------------------------------------------------------------------------------------------------
5904 // Load required JS libraries and CSS
5906 NETDATA.requiredJs = [
5908 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5910 isAlreadyLoaded: function() {
5911 // check if bootstrap is loaded
5912 if(typeof $().emulateTransitionEnd == 'function')
5915 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5923 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5924 isAlreadyLoaded: function() { return false; }
5928 NETDATA.requiredCSS = [
5930 url: NETDATA.themes.current.bootstrap_css,
5931 isAlreadyLoaded: function() {
5932 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5939 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5940 isAlreadyLoaded: function() { return false; }
5943 url: NETDATA.themes.current.dashboard_css,
5944 isAlreadyLoaded: function() { return false; }
5948 NETDATA.loadedRequiredJs = 0;
5949 NETDATA.loadRequiredJs = function(index, callback) {
5950 if(index >= NETDATA.requiredJs.length) {
5951 if(typeof callback === 'function')
5956 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5957 NETDATA.loadedRequiredJs++;
5958 NETDATA.loadRequiredJs(++index, callback);
5962 if(NETDATA.options.debug.main_loop === true)
5963 console.log('loading ' + NETDATA.requiredJs[index].url);
5966 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5970 url: NETDATA.requiredJs[index].url,
5973 xhrFields: { withCredentials: true } // required for the cookie
5976 if(NETDATA.options.debug.main_loop === true)
5977 console.log('loaded ' + NETDATA.requiredJs[index].url);
5980 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5982 .always(function() {
5983 NETDATA.loadedRequiredJs++;
5986 NETDATA.loadRequiredJs(++index, callback);
5990 NETDATA.loadRequiredJs(++index, callback);
5993 NETDATA.loadRequiredCSS = function(index) {
5994 if(index >= NETDATA.requiredCSS.length)
5997 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5998 NETDATA.loadRequiredCSS(++index);
6002 if(NETDATA.options.debug.main_loop === true)
6003 console.log('loading ' + NETDATA.requiredCSS[index].url);
6005 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6006 NETDATA.loadRequiredCSS(++index);
6010 // ----------------------------------------------------------------------------------------------------------------
6011 // Registry of netdata hosts
6014 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6015 chart_div_offset: 100, // give that space above the chart when scrolling to it
6016 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6017 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6019 ms_penalty: 0, // the time penalty of the next alarm
6020 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6021 // if alarms are shown faster than: one per 500ms
6023 notifications: false, // when true, the browser supports notifications (may not be granted though)
6024 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6025 first_notification_id: 0, // the id of the first alarm_log entry for this session
6026 // this is used to prevent CLEAR notifications for past events
6027 // notifications_shown: new Array(),
6029 server: null, // the server to connect to for fetching alarms
6030 current: null, // the list of raised alarms - updated in the background
6031 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6033 notify: function(entry) {
6034 // console.log('alarm ' + entry.unique_id);
6036 if(entry.updated === true) {
6037 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6041 var value = entry.value;
6042 if(NETDATA.alarms.current !== null) {
6043 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6044 if(typeof t !== 'undefined' && entry.status == t.status)
6048 var name = entry.name.replace(/_/g, ' ');
6049 var status = entry.status.toLowerCase();
6050 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6051 var tag = entry.alarm_id;
6052 var icon = 'images/seo-performance-128.png';
6053 var interaction = false;
6057 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6059 switch(entry.status) {
6067 case 'UNINITIALIZED':
6071 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6072 // console.log('alarm ' + entry.unique_id + ' is not current');
6075 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6076 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6079 title = name + ' back to normal';
6080 icon = 'images/check-mark-2-128-green.png'
6081 interaction = false;
6085 if(entry.old_status === 'CRITICAL')
6086 status = 'demoted to ' + entry.status.toLowerCase();
6088 icon = 'images/alert-128-orange.png';
6089 interaction = false;
6093 if(entry.old_status === 'WARNING')
6094 status = 'escalated to ' + entry.status.toLowerCase();
6096 icon = 'images/alert-128-red.png'
6101 console.log('invalid alarm status ' + entry.status);
6106 // cleanup old notifications with the same alarm_id as this one
6107 // FIXME: it does not seem to work on any web browser!
6108 var len = NETDATA.alarms.notifications_shown.length;
6110 var n = NETDATA.alarms.notifications_shown[len];
6111 if(n.data.alarm_id === entry.alarm_id) {
6112 console.log('removing old alarm ' + n.data.unique_id);
6114 // close the notification
6117 // remove it from the array
6118 NETDATA.alarms.notifications_shown.splice(len, 1);
6119 len = NETDATA.alarms.notifications_shown.length;
6126 setTimeout(function() {
6127 // show this notification
6128 // console.log('new notification: ' + title);
6129 var n = new Notification(title, {
6130 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6132 requireInteraction: interaction,
6133 icon: NETDATA.serverDefault + icon,
6137 n.onclick = function(event) {
6138 event.preventDefault();
6139 NETDATA.alarms.onclick(event.target.data);
6143 // NETDATA.alarms.notifications_shown.push(n);
6144 // console.log(entry);
6145 }, NETDATA.alarms.ms_penalty);
6147 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6151 scrollToChart: function(chart_id) {
6152 if(typeof chart_id === 'string') {
6153 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6154 if(typeof offset !== 'undefined') {
6155 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6162 scrollToAlarm: function(alarm) {
6163 if(typeof alarm === 'object') {
6164 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6166 if(ret === true && NETDATA.options.page_is_visible === false)
6168 // 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.');
6173 notifyAll: function() {
6174 // console.log('FETCHING ALARM LOG');
6175 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6176 // console.log('ALARM LOG FETCHED');
6178 if(data === null || typeof data !== 'object') {
6179 console.log('invalid alarms log response');
6183 if(data.length === 0) {
6184 console.log('received empty alarm log');
6188 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6190 data.sort(function(a, b) {
6191 if(a.unique_id > b.unique_id) return -1;
6192 if(a.unique_id < b.unique_id) return 1;
6196 NETDATA.alarms.ms_penalty = 0;
6198 var len = data.length;
6200 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6201 NETDATA.alarms.notify(data[len]);
6204 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6207 NETDATA.alarms.last_notification_id = data[0].unique_id;
6208 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6209 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6213 check_notifications: function() {
6214 // returns true if we should fire 1+ notifications
6216 if(NETDATA.alarms.notifications !== true) {
6217 // console.log('notifications not available');
6221 if(Notification.permission !== 'granted') {
6222 // console.log('notifications not granted');
6226 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6227 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6229 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6230 // console.log('new alarms detected');
6233 //else console.log('no new alarms');
6235 // else console.log('cannot process alarms');
6240 get: function(what, callback) {
6242 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6246 'Cache-Control': 'no-cache, no-store',
6247 'Pragma': 'no-cache'
6249 xhrFields: { withCredentials: true } // required for the cookie
6251 .done(function(data) {
6252 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6253 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6255 if(typeof callback === 'function')
6259 NETDATA.error(415, NETDATA.alarms.server);
6261 if(typeof callback === 'function')
6266 update_forever: function() {
6267 NETDATA.alarms.get('active', function(data) {
6269 NETDATA.alarms.current = data;
6271 if(NETDATA.alarms.check_notifications() === true) {
6272 NETDATA.alarms.notifyAll();
6275 if (typeof NETDATA.alarms.callback === 'function') {
6276 NETDATA.alarms.callback(data);
6279 // Health monitoring is disabled on this netdata
6280 if(data.status === false) return;
6283 setTimeout(NETDATA.alarms.update_forever, 10000);
6287 get_log: function(last_id, callback) {
6288 // console.log('fetching all log after ' + last_id.toString());
6290 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6294 'Cache-Control': 'no-cache, no-store',
6295 'Pragma': 'no-cache'
6297 xhrFields: { withCredentials: true } // required for the cookie
6299 .done(function(data) {
6300 if(typeof callback === 'function')
6304 NETDATA.error(416, NETDATA.alarms.server);
6306 if(typeof callback === 'function')
6312 var host = NETDATA.serverDefault;
6313 while(host.slice(-1) === '/')
6314 host = host.substring(0, host.length - 1);
6315 NETDATA.alarms.server = host;
6317 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6319 if(NETDATA.alarms.onclick === null)
6320 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6322 if(netdataShowAlarms === true) {
6323 NETDATA.alarms.update_forever();
6325 if('Notification' in window) {
6326 // console.log('notifications available');
6327 NETDATA.alarms.notifications = true;
6329 if(Notification.permission === 'default')
6330 Notification.requestPermission();
6336 // ----------------------------------------------------------------------------------------------------------------
6337 // Registry of netdata hosts
6339 NETDATA.registry = {
6340 server: null, // the netdata registry server
6341 person_guid: null, // the unique ID of this browser / user
6342 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6343 hostname: null, // the hostname of the netdata server that served dashboard.js
6344 machines: null, // the user's other URLs
6345 machines_array: null, // the user's other URLs in an array
6348 parsePersonUrls: function(person_urls) {
6349 // console.log(person_urls);
6350 NETDATA.registry.person_urls = person_urls;
6353 NETDATA.registry.machines = {};
6354 NETDATA.registry.machines_array = new Array();
6356 var now = Date.now();
6357 var apu = person_urls;
6360 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6361 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6367 accesses: apu[i][3],
6369 alternate_urls: new Array()
6371 obj.alternate_urls.push(apu[i][1]);
6373 NETDATA.registry.machines[apu[i][0]] = obj;
6374 NETDATA.registry.machines_array.push(obj);
6377 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6379 var pu = NETDATA.registry.machines[apu[i][0]];
6380 if(pu.last_t < apu[i][2]) {
6382 pu.last_t = apu[i][2];
6383 pu.name = apu[i][4];
6385 pu.accesses += apu[i][3];
6386 pu.alternate_urls.push(apu[i][1]);
6391 if(typeof netdataRegistryCallback === 'function')
6392 netdataRegistryCallback(NETDATA.registry.machines_array);
6396 if(netdataRegistry !== true) return;
6398 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6400 NETDATA.registry.server = data.registry;
6401 NETDATA.registry.machine_guid = data.machine_guid;
6402 NETDATA.registry.hostname = data.hostname;
6404 NETDATA.registry.access(2, function (person_urls) {
6405 NETDATA.registry.parsePersonUrls(person_urls);
6412 hello: function(host, callback) {
6413 while(host.slice(-1) === '/')
6414 host = host.substring(0, host.length - 1);
6416 // send HELLO to a netdata server:
6417 // 1. verifies the server is reachable
6418 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6420 url: host + '/api/v1/registry?action=hello',
6424 'Cache-Control': 'no-cache, no-store',
6425 'Pragma': 'no-cache'
6427 xhrFields: { withCredentials: true } // required for the cookie
6429 .done(function(data) {
6430 if(typeof data.status !== 'string' || data.status !== 'ok') {
6431 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6435 if(typeof callback === 'function')
6439 NETDATA.error(407, host);
6441 if(typeof callback === 'function')
6446 access: function(max_redirects, callback) {
6447 // send ACCESS to a netdata registry:
6448 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6449 // 2. it responds with a list of netdata servers we know
6450 // the registry identifies us using a cookie it sets the first time we access it
6451 // the registry may respond with a redirect URL to send us to another registry
6453 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),
6457 'Cache-Control': 'no-cache, no-store',
6458 'Pragma': 'no-cache'
6460 xhrFields: { withCredentials: true } // required for the cookie
6462 .done(function(data) {
6463 var redirect = null;
6464 if(typeof data.registry === 'string')
6465 redirect = data.registry;
6467 if(typeof data.status !== 'string' || data.status !== 'ok') {
6468 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6473 if(redirect !== null && max_redirects > 0) {
6474 NETDATA.registry.server = redirect;
6475 NETDATA.registry.access(max_redirects - 1, callback);
6478 if(typeof callback === 'function')
6483 if(typeof data.person_guid === 'string')
6484 NETDATA.registry.person_guid = data.person_guid;
6486 if(typeof callback === 'function')
6487 callback(data.urls);
6491 NETDATA.error(410, NETDATA.registry.server);
6493 if(typeof callback === 'function')
6498 delete: function(delete_url, callback) {
6499 // send DELETE to a netdata registry:
6501 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),
6505 'Cache-Control': 'no-cache, no-store',
6506 'Pragma': 'no-cache'
6508 xhrFields: { withCredentials: true } // required for the cookie
6510 .done(function(data) {
6511 if(typeof data.status !== 'string' || data.status !== 'ok') {
6512 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6516 if(typeof callback === 'function')
6520 NETDATA.error(412, NETDATA.registry.server);
6522 if(typeof callback === 'function')
6527 search: function(machine_guid, callback) {
6528 // SEARCH for the URLs of a machine:
6530 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,
6534 'Cache-Control': 'no-cache, no-store',
6535 'Pragma': 'no-cache'
6537 xhrFields: { withCredentials: true } // required for the cookie
6539 .done(function(data) {
6540 if(typeof data.status !== 'string' || data.status !== 'ok') {
6541 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6545 if(typeof callback === 'function')
6549 NETDATA.error(418, NETDATA.registry.server);
6551 if(typeof callback === 'function')
6556 switch: function(new_person_guid, callback) {
6559 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,
6563 'Cache-Control': 'no-cache, no-store',
6564 'Pragma': 'no-cache'
6566 xhrFields: { withCredentials: true } // required for the cookie
6568 .done(function(data) {
6569 if(typeof data.status !== 'string' || data.status !== 'ok') {
6570 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6574 if(typeof callback === 'function')
6578 NETDATA.error(414, NETDATA.registry.server);
6580 if(typeof callback === 'function')
6586 // ----------------------------------------------------------------------------------------------------------------
6589 if(typeof netdataPrepCallback === 'function')
6590 netdataPrepCallback();
6592 NETDATA.errorReset();
6593 NETDATA.loadRequiredCSS(0);
6595 NETDATA._loadjQuery(function() {
6596 NETDATA.loadRequiredJs(0, function() {
6597 if(typeof $().emulateTransitionEnd !== 'function') {
6598 // bootstrap is not available
6599 NETDATA.options.current.show_help = false;
6602 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6603 if(NETDATA.options.debug.main_loop === true)
6604 console.log('starting chart refresh thread');
6610 })(window, document);