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
32 //(function(window, document, undefined) {
34 // ------------------------------------------------------------------------
35 // compatibility fixes
37 // fix IE issue with console
38 if(!window.console) { window.console = { log: function(){} }; }
40 // if string.endsWith is not defined, define it
41 if(typeof String.prototype.endsWith !== 'function') {
42 String.prototype.endsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(-s.length) === s;
48 // if string.startsWith is not defined, define it
49 if(typeof String.prototype.startsWith !== 'function') {
50 String.prototype.startsWith = function(s) {
51 if(s.length > this.length) return false;
52 return this.slice(s.length) === s;
57 var NETDATA = window.NETDATA || {};
59 NETDATA.name2id = function(s) {
68 // ----------------------------------------------------------------------------------------------------------------
69 // Detect the netdata server
71 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73 NETDATA._scriptSource = function() {
76 if(typeof document.currentScript !== 'undefined') {
77 script = document.currentScript;
80 var all_scripts = document.getElementsByTagName('script');
81 script = all_scripts[all_scripts.length - 1];
84 if (typeof script.getAttribute.length !== 'undefined')
87 script = script.getAttribute('src', -1);
92 if(typeof netdataServer !== 'undefined')
93 NETDATA.serverDefault = netdataServer;
95 var s = NETDATA._scriptSource();
96 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
98 console.log('WARNING: Cannot detect the URL of the netdata server.');
99 NETDATA.serverDefault = null;
103 if(NETDATA.serverDefault === null)
104 NETDATA.serverDefault = '';
105 else if(NETDATA.serverDefault.slice(-1) !== '/')
106 NETDATA.serverDefault += '/';
108 // default URLs for all the external files we need
109 // make them RELATIVE so that the whole thing can also be
110 // installed under a web server
111 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124 NETDATA.google_js = 'https://www.google.com/jsapi';
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161226-1',
130 background: '#FFFFFF',
131 foreground: '#000000',
134 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
135 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
136 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
137 '#329262', '#3B3EAC' ],
138 easypiechart_track: '#f0f0f0',
139 easypiechart_scale: '#dfe0e0',
140 gauge_pointer: '#C0C0C0',
141 gauge_stroke: '#F0F0F0',
142 gauge_gradient: false
145 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161218-2',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161226-1',
147 background: '#272b30',
148 foreground: '#C8C8C8',
151 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
152 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
153 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
154 '#a6a479', '#a66da8' ],
156 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
157 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
158 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
159 '#329262', '#3B3EFF' ],
160 easypiechart_track: '#373b40',
161 easypiechart_scale: '#373b40',
162 gauge_pointer: '#474b50',
163 gauge_stroke: '#373b40',
164 gauge_gradient: false
168 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169 NETDATA.themes.current = NETDATA.themes[netdataTheme];
171 NETDATA.themes.current = NETDATA.themes.white;
173 NETDATA.colors = NETDATA.themes.current.colors;
175 // these are the colors Google Charts are using
176 // we have them here to attempt emulate their look and feel on the other chart libraries
177 // http://there4.io/2012/05/02/google-chart-color-list/
178 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
182 // an alternative set
183 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
185 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
187 if(typeof netdataShowHelp === 'undefined')
188 netdataShowHelp = true;
190 if(typeof netdataShowAlarms === 'undefined')
191 netdataShowAlarms = false;
193 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194 netdataRegistryAfterMs = 1500;
196 if(typeof netdataRegistry === 'undefined') {
197 // backward compatibility
198 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199 netdataRegistry = true;
201 netdataRegistry = false;
203 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204 netdataRegistry = true;
206 // ----------------------------------------------------------------------------------------------------------------
207 // the defaults for all charts
209 // if the user does not specify any of these, the following will be used
211 NETDATA.chartDefaults = {
212 host: NETDATA.serverDefault, // the server to get data from
213 width: '100%', // the chart width - can be null
214 height: '100%', // the chart height - can be null
215 min_width: null, // the chart minimum width - can be null
216 library: 'dygraph', // the graphing library to use
217 method: 'average', // the grouping method
218 before: 0, // panning
219 after: -600, // panning
220 pixels_per_point: 1, // the detail of the chart
221 fill_luminance: 0.8 // luminance of colors in solit areas
224 // ----------------------------------------------------------------------------------------------------------------
228 pauseCallback: null, // a callback when we are really paused
230 pause: false, // when enabled we don't auto-refresh the charts
232 targets: null, // an array of all the state objects that are
233 // currently active (independently of their
234 // viewport visibility)
236 updated_dom: true, // when true, the DOM has been updated with
237 // new elements we have to check.
239 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
240 // rendering charts continiously.
241 // used with .current.fast_render_timeframe
243 page_is_visible: true, // when true, this page is visible
245 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
246 // auto-refresher for some time (when a chart is
247 // performing pan or zoom, we need to stop refreshing
248 // all other charts, to have the maximum speed for
249 // rendering the chart that is panned or zoomed).
250 // Used with .current.global_pan_sync_time
252 last_resized: Date.now(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false, // kill pending ajax page scroll
332 async_on_scroll: false, // sync/async onscroll handler
333 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
335 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
337 setOptionCallback: function() { ; }
345 chart_data_url: false,
346 chart_errors: false, // FIXME
354 NETDATA.statistics = {
357 refreshes_active_max: 0
361 // ----------------------------------------------------------------------------------------------------------------
362 // local storage options
364 NETDATA.localStorage = {
367 callback: {} // only used for resetting back to defaults
370 NETDATA.localStorageGet = function(key, def, callback) {
373 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
374 NETDATA.localStorage.default[key.toString()] = def;
375 NETDATA.localStorage.callback[key.toString()] = callback;
378 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
380 // console.log('localStorage: loading "' + key.toString() + '"');
381 ret = localStorage.getItem(key.toString());
382 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
383 if(ret === null || ret === 'undefined') {
384 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
385 localStorage.setItem(key.toString(), JSON.stringify(def));
389 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
390 ret = JSON.parse(ret);
391 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
395 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
400 if(typeof ret === 'undefined' || ret === 'undefined') {
401 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
405 NETDATA.localStorage.current[key.toString()] = ret;
409 NETDATA.localStorageSet = function(key, value, callback) {
410 if(typeof value === 'undefined' || value === 'undefined') {
411 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
414 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
415 NETDATA.localStorage.default[key.toString()] = value;
416 NETDATA.localStorage.current[key.toString()] = value;
417 NETDATA.localStorage.callback[key.toString()] = callback;
420 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
421 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
423 localStorage.setItem(key.toString(), JSON.stringify(value));
426 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
430 NETDATA.localStorage.current[key.toString()] = value;
434 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
436 if(typeof obj[i] === 'object') {
437 //console.log('object ' + prefix + '.' + i.toString());
438 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
442 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
446 NETDATA.setOption = function(key, value) {
447 if(key.toString() === 'setOptionCallback') {
448 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
449 NETDATA.options.current[key.toString()] = value;
450 NETDATA.options.current.setOptionCallback();
453 else if(NETDATA.options.current[key.toString()] !== value) {
454 var name = 'options.' + key.toString();
456 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
457 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
459 //console.log(NETDATA.localStorage);
460 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
461 //console.log(NETDATA.options);
462 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
464 if(typeof NETDATA.options.current.setOptionCallback === 'function')
465 NETDATA.options.current.setOptionCallback();
471 NETDATA.getOption = function(key) {
472 return NETDATA.options.current[key.toString()];
475 // read settings from local storage
476 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
478 // always start with this option enabled.
479 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
481 NETDATA.resetOptions = function() {
482 for(var i in NETDATA.localStorage.default) {
483 var a = i.split('.');
485 if(a[0] === 'options') {
486 if(a[1] === 'setOptionCallback') continue;
487 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
488 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
490 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
492 else if(a[0] === 'chart_heights') {
493 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
494 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
500 // ----------------------------------------------------------------------------------------------------------------
502 if(NETDATA.options.debug.main_loop === true)
503 console.log('welcome to NETDATA');
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
510 NETDATA.onscroll_updater_count = 0;
511 NETDATA.onscroll_updater_running = false;
512 NETDATA.onscroll_updater_last_run = 0;
513 NETDATA.onscroll_updater_watchdog = null;
514 NETDATA.onscroll_updater_max_duration = 0;
515 NETDATA.onscroll_updater_above_threshold_count = 0;
516 NETDATA.onscroll_updater = function() {
517 NETDATA.onscroll_updater_running = true;
518 NETDATA.onscroll_updater_count++;
519 var start = Date.now();
521 var targets = NETDATA.options.targets;
522 var len = targets.length;
524 // when the user scrolls he sees that we have
525 // hidden all the not-visible charts
526 // using this little function we try to switch
527 // the charts back to visible quickly
530 if(NETDATA.options.abort_ajax_on_scroll === true) {
531 // we have to cancel pending requests too
534 if (targets[len]._updating === true) {
535 if (typeof targets[len].xhr !== 'undefined') {
536 targets[len].xhr.abort();
537 targets[len].running = false;
538 targets[len]._updating = false;
540 targets[len].isVisible();
545 // just find which chart is visible
548 targets[len].isVisible();
551 var end = Date.now();
552 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
554 if(NETDATA.options.current.async_on_scroll === false) {
555 var dt = end - start;
556 if(dt > NETDATA.onscroll_updater_max_duration) {
557 // console.log('max onscroll event handler duration increased to ' + dt);
558 NETDATA.onscroll_updater_max_duration = dt;
561 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
562 // console.log('slow: ' + dt);
563 NETDATA.onscroll_updater_above_threshold_count++;
565 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
566 NETDATA.setOption('async_on_scroll', true);
567 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
572 NETDATA.onscroll_updater_last_run = start;
573 NETDATA.onscroll_updater_running = false;
576 NETDATA.onscroll = function() {
577 // console.log('onscroll');
579 NETDATA.options.last_page_scroll = Date.now();
580 NETDATA.options.auto_refresher_stop_until = 0;
582 if(NETDATA.options.targets === null) return;
584 if(NETDATA.options.current.async_on_scroll === true) {
586 if(NETDATA.onscroll_updater_running === false) {
587 NETDATA.onscroll_updater_running = true;
588 setTimeout(NETDATA.onscroll_updater, 0);
591 if(NETDATA.onscroll_updater_watchdog !== null)
592 clearTimeout(NETDATA.onscroll_updater_watchdog);
594 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
595 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
596 // console.log('watchdog');
597 NETDATA.onscroll_updater();
600 NETDATA.onscroll_updater_watchdog = null;
606 NETDATA.onscroll_updater();
610 window.onresize = NETDATA.onresize;
611 window.onscroll = NETDATA.onscroll;
613 // ----------------------------------------------------------------------------------------------------------------
616 NETDATA.errorCodes = {
617 100: { message: "Cannot load chart library", alert: true },
618 101: { message: "Cannot load jQuery", alert: true },
619 402: { message: "Chart library not found", alert: false },
620 403: { message: "Chart library not enabled/is failed", alert: false },
621 404: { message: "Chart not found", alert: false },
622 405: { message: "Cannot download charts index from server", alert: true },
623 406: { message: "Invalid charts index downloaded from server", alert: true },
624 407: { message: "Cannot HELLO netdata server", alert: false },
625 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
626 409: { message: "Cannot ACCESS netdata registry", alert: false },
627 410: { message: "Netdata registry ACCESS failed", alert: false },
628 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
629 412: { message: "Netdata registry DELETE failed", alert: false },
630 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
631 414: { message: "Netdata registry SWITCH failed", alert: false },
632 415: { message: "Netdata alarms download failed", alert: false },
633 416: { message: "Netdata alarms log download failed", alert: false },
634 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
635 418: { message: "Netdata registry SEARCH failed", alert: false }
637 NETDATA.errorLast = {
643 NETDATA.error = function(code, msg) {
644 NETDATA.errorLast.code = code;
645 NETDATA.errorLast.message = msg;
646 NETDATA.errorLast.datetime = Date.now();
648 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
651 if(typeof netdataErrorCallback === 'function') {
652 ret = netdataErrorCallback('system', code, msg);
655 if(ret && NETDATA.errorCodes[code].alert)
656 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
659 NETDATA.errorReset = function() {
660 NETDATA.errorLast.code = 0;
661 NETDATA.errorLast.message = "You are doing fine!";
662 NETDATA.errorLast.datetime = 0;
665 // ----------------------------------------------------------------------------------------------------------------
666 // commonMin & commonMax
668 NETDATA.commonMin = {
672 get: function(state) {
673 if(typeof state.__commonMin === 'undefined') {
674 // get the commonMin setting
675 var self = $(state.element);
676 state.__commonMin = self.data('common-min') || null;
679 var min = state.data.min;
680 var name = state.__commonMin;
683 // we don't need commonMin
684 //state.log('no need for commonMin');
688 var t = this.keys[name];
689 if(typeof t === 'undefined') {
691 this.keys[name] = {};
695 var uuid = state.uuid;
696 if(typeof t[uuid] !== 'undefined') {
697 if(t[uuid] === min) {
698 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
699 return this.latest[name];
701 else if(min < this.latest[name]) {
702 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
704 this.latest[name] = min;
712 // find the common min
715 if(t[i] < m) m = t[i];
717 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
718 this.latest[name] = m;
723 NETDATA.commonMax = {
727 get: function(state) {
728 if(typeof state.__commonMax === 'undefined') {
729 // get the commonMax setting
730 var self = $(state.element);
731 state.__commonMax = self.data('common-max') || null;
734 var max = state.data.max;
735 var name = state.__commonMax;
738 // we don't need commonMax
739 //state.log('no need for commonMax');
743 var t = this.keys[name];
744 if(typeof t === 'undefined') {
746 this.keys[name] = {};
750 var uuid = state.uuid;
751 if(typeof t[uuid] !== 'undefined') {
752 if(t[uuid] === max) {
753 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
754 return this.latest[name];
756 else if(max > this.latest[name]) {
757 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
759 this.latest[name] = max;
767 // find the common max
770 if(t[i] > m) m = t[i];
772 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
773 this.latest[name] = m;
778 // ----------------------------------------------------------------------------------------------------------------
781 // When multiple charts need the same chart, we avoid downloading it
782 // multiple times (and having it in browser memory multiple time)
783 // by using this registry.
785 // Every time we download a chart definition, we save it here with .add()
786 // Then we try to get it back with .get(). If that fails, we download it.
788 NETDATA.chartRegistry = {
791 fixid: function(id) {
792 return id.replace(/:/g, "_").replace(/\//g, "_");
795 add: function(host, id, data) {
796 host = this.fixid(host);
799 if(typeof this.charts[host] === 'undefined')
800 this.charts[host] = {};
802 //console.log('added ' + host + '/' + id);
803 this.charts[host][id] = data;
806 get: function(host, id) {
807 host = this.fixid(host);
810 if(typeof this.charts[host] === 'undefined')
813 if(typeof this.charts[host][id] === 'undefined')
816 //console.log('cached ' + host + '/' + id);
817 return this.charts[host][id];
820 downloadAll: function(host, callback) {
821 while(host.slice(-1) === '/')
822 host = host.substring(0, host.length - 1);
827 url: host + '/api/v1/charts',
830 xhrFields: { withCredentials: true } // required for the cookie
832 .done(function(data) {
834 var h = NETDATA.chartRegistry.fixid(host);
835 self.charts[h] = data.charts;
837 else NETDATA.error(406, host + '/api/v1/charts');
839 if(typeof callback === 'function')
843 NETDATA.error(405, host + '/api/v1/charts');
845 if(typeof callback === 'function')
851 // ----------------------------------------------------------------------------------------------------------------
852 // Global Pan and Zoom on charts
854 // Using this structure are synchronize all the charts, so that
855 // when you pan or zoom one, all others are automatically refreshed
856 // to the same timespan.
858 NETDATA.globalPanAndZoom = {
859 seq: 0, // timestamp ms
860 // every time a chart is panned or zoomed
861 // we set the timestamp here
862 // then we use it as a sequence number
863 // to find if other charts are syncronized
866 master: null, // the master chart (state), to which all others
869 force_before_ms: null, // the timespan to sync all other charts
870 force_after_ms: null,
875 setMaster: function(state, after, before) {
876 if(NETDATA.options.current.sync_pan_and_zoom === false)
879 if(this.master !== null && this.master !== state)
880 this.master.resetChart(true, true);
882 var now = Date.now();
885 this.force_after_ms = after;
886 this.force_before_ms = before;
887 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
889 if(typeof this.callback === 'function')
890 this.callback(true, after, before);
894 clearMaster: function() {
895 if(this.master !== null) {
896 var st = this.master;
903 this.force_after_ms = null;
904 this.force_before_ms = null;
905 NETDATA.options.auto_refresher_stop_until = 0;
907 if(typeof this.callback === 'function')
908 this.callback(false, 0, 0);
911 // is the given state the master of the global
912 // pan and zoom sync?
913 isMaster: function(state) {
914 if(this.master === state) return true;
918 // are we currently have a global pan and zoom sync?
919 isActive: function() {
920 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
924 // check if a chart, other than the master
925 // needs to be refreshed, due to the global pan and zoom
926 shouldBeAutoRefreshed: function(state) {
927 if(this.master === null || this.seq === 0)
930 //if(state.needsRecreation())
933 if(state.tm.pan_and_zoom_seq === this.seq)
940 // ----------------------------------------------------------------------------------------------------------------
941 // dimensions selection
944 // move color assignment to dimensions, here
946 dimensionStatus = function(parent, label, name_div, value_div, color) {
947 this.enabled = false;
948 this.parent = parent;
950 this.name_div = null;
951 this.value_div = null;
952 this.color = NETDATA.themes.current.foreground;
954 if(parent.unselected_count === 0)
955 this.selected = true;
957 this.selected = false;
959 this.setOptions(name_div, value_div, color);
962 dimensionStatus.prototype.invalidate = function() {
963 this.name_div = null;
964 this.value_div = null;
965 this.enabled = false;
968 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
971 if(this.name_div != name_div) {
972 this.name_div = name_div;
973 this.name_div.title = this.label;
974 this.name_div.style.color = this.color;
975 if(this.selected === false)
976 this.name_div.className = 'netdata-legend-name not-selected';
978 this.name_div.className = 'netdata-legend-name selected';
981 if(this.value_div != value_div) {
982 this.value_div = value_div;
983 this.value_div.title = this.label;
984 this.value_div.style.color = this.color;
985 if(this.selected === false)
986 this.value_div.className = 'netdata-legend-value not-selected';
988 this.value_div.className = 'netdata-legend-value selected';
995 dimensionStatus.prototype.setHandler = function() {
996 if(this.enabled === false) return;
1000 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1001 this.name_div.onclick = this.value_div.onclick = function(e) {
1003 if(ds.isSelected()) {
1005 if(e.shiftKey === true || e.ctrlKey === true) {
1006 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1009 if(ds.parent.countSelected() === 0)
1010 ds.parent.selectAll();
1013 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1014 if(ds.parent.countSelected() === 1) {
1015 ds.parent.selectAll();
1018 ds.parent.selectNone();
1024 // this is not selected
1025 if(e.shiftKey === true || e.ctrlKey === true) {
1026 // control or shift key is pressed -> select this too
1030 // no key is pressed -> select only this
1031 ds.parent.selectNone();
1036 ds.parent.state.redrawChart();
1040 dimensionStatus.prototype.select = function() {
1041 if(this.enabled === false) return;
1043 this.name_div.className = 'netdata-legend-name selected';
1044 this.value_div.className = 'netdata-legend-value selected';
1045 this.selected = true;
1048 dimensionStatus.prototype.unselect = function() {
1049 if(this.enabled === false) return;
1051 this.name_div.className = 'netdata-legend-name not-selected';
1052 this.value_div.className = 'netdata-legend-value hidden';
1053 this.selected = false;
1056 dimensionStatus.prototype.isSelected = function() {
1057 return(this.enabled === true && this.selected === true);
1060 // ----------------------------------------------------------------------------------------------------------------
1062 dimensionsVisibility = function(state) {
1065 this.dimensions = {};
1066 this.selected_count = 0;
1067 this.unselected_count = 0;
1070 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1071 if(typeof this.dimensions[label] === 'undefined') {
1073 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1076 this.dimensions[label].setOptions(name_div, value_div, color);
1078 return this.dimensions[label];
1081 dimensionsVisibility.prototype.dimensionGet = function(label) {
1082 return this.dimensions[label];
1085 dimensionsVisibility.prototype.invalidateAll = function() {
1086 for(var d in this.dimensions)
1087 this.dimensions[d].invalidate();
1090 dimensionsVisibility.prototype.selectAll = function() {
1091 for(var d in this.dimensions)
1092 this.dimensions[d].select();
1095 dimensionsVisibility.prototype.countSelected = function() {
1097 for(var d in this.dimensions)
1098 if(this.dimensions[d].isSelected()) i++;
1103 dimensionsVisibility.prototype.selectNone = function() {
1104 for(var d in this.dimensions)
1105 this.dimensions[d].unselect();
1108 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1109 var ret = new Array();
1110 this.selected_count = 0;
1111 this.unselected_count = 0;
1113 var len = array.length;
1115 var ds = this.dimensions[array[len]];
1116 if(typeof ds === 'undefined') {
1117 // console.log(array[i] + ' is not found');
1120 else if(ds.isSelected()) {
1122 this.selected_count++;
1126 this.unselected_count++;
1130 if(this.selected_count === 0 && this.unselected_count !== 0) {
1132 return this.selected2BooleanArray(array);
1139 // ----------------------------------------------------------------------------------------------------------------
1140 // global selection sync
1142 NETDATA.globalSelectionSync = {
1144 dont_sync_before: 0,
1149 if(this.state !== null)
1150 this.state.globalSelectionSyncStop();
1154 if(this.state !== null) {
1155 this.state.globalSelectionSyncDelay();
1160 // ----------------------------------------------------------------------------------------------------------------
1161 // Our state object, where all per-chart values are stored
1163 chartState = function(element) {
1164 var self = $(element);
1165 this.element = element;
1168 // all private functions should use 'that', instead of 'this'
1171 /* error() - private
1172 * show an error instead of the chart
1174 var error = function(msg) {
1177 if(typeof netdataErrorCallback === 'function') {
1178 ret = netdataErrorCallback('chart', that.id, msg);
1182 that.element.innerHTML = that.id + ': ' + msg;
1183 that.enabled = false;
1184 that.current = that.pan;
1188 // GUID - a unique identifier for the chart
1189 this.uuid = NETDATA.guid();
1191 // string - the name of chart
1192 this.id = self.data('netdata');
1194 // string - the key for localStorage settings
1195 this.settings_id = self.data('id') || null;
1197 // the user given dimensions of the element
1198 this.width = self.data('width') || NETDATA.chartDefaults.width;
1199 this.height = self.data('height') || NETDATA.chartDefaults.height;
1201 if(this.settings_id !== null) {
1202 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1203 // this is the callback that will be called
1204 // if and when the user resets all localStorage variables
1205 // to their defaults
1207 resizeChartToHeight(height);
1211 // string - the netdata server URL, without any path
1212 this.host = self.data('host') || NETDATA.chartDefaults.host;
1214 // make sure the host does not end with /
1215 // all netdata API requests use absolute paths
1216 while(this.host.slice(-1) === '/')
1217 this.host = this.host.substring(0, this.host.length - 1);
1219 // string - the grouping method requested by the user
1220 this.method = self.data('method') || NETDATA.chartDefaults.method;
1222 // the time-range requested by the user
1223 this.after = self.data('after') || NETDATA.chartDefaults.after;
1224 this.before = self.data('before') || NETDATA.chartDefaults.before;
1226 // the pixels per point requested by the user
1227 this.pixels_per_point = self.data('pixels-per-point') || 1;
1228 this.points = self.data('points') || null;
1230 // the dimensions requested by the user
1231 this.dimensions = self.data('dimensions') || null;
1233 // the chart library requested by the user
1234 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1236 // how many retries we have made to load chart data from the server
1237 this.retries_on_data_failures = 0;
1239 // object - the chart library used
1240 this.library = null;
1244 this.colors_assigned = {};
1245 this.colors_available = null;
1247 // the element already created by the user
1248 this.element_message = null;
1250 // the element with the chart
1251 this.element_chart = null;
1253 // the element with the legend of the chart (if created by us)
1254 this.element_legend = null;
1255 this.element_legend_childs = {
1265 this.chart_url = null; // string - the url to download chart info
1266 this.chart = null; // object - the chart as downloaded from the server
1268 this.title = self.data('title') || null; // the title of the chart
1269 this.units = self.data('units') || null; // the units of the chart dimensions
1270 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1271 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1273 this.running = false; // boolean - true when the chart is being refreshed now
1274 this.validated = false; // boolean - has the chart been validated?
1275 this.enabled = true; // boolean - is the chart enabled for refresh?
1276 this.paused = false; // boolean - is the chart paused for any reason?
1277 this.selected = false; // boolean - is the chart shown a selection?
1278 this.debug = false; // boolean - console.log() debug info about this chart
1280 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1281 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1282 this.requested_after = null; // milliseconds - the timestamp of the request after param
1283 this.requested_before = null; // milliseconds - the timestamp of the request before param
1284 this.requested_padding = null;
1285 this.view_after = 0;
1286 this.view_before = 0;
1288 this.value_decimal_detail = -1;
1290 var d = self.data('decimal-digits');
1291 if(typeof d === 'number') {
1292 this.value_decimal_detail = 1;
1294 this.value_decimal_detail *= 10;
1301 force_update_at: 0, // the timestamp to force the update at
1302 force_before_ms: null,
1303 force_after_ms: null
1308 force_update_at: 0, // the timestamp to force the update at
1309 force_before_ms: null,
1310 force_after_ms: null
1315 force_update_at: 0, // the timestamp to force the update at
1316 force_before_ms: null,
1317 force_after_ms: null
1320 // this is a pointer to one of the sub-classes below
1322 this.current = this.auto;
1324 // check the requested library is available
1325 // we don't initialize it here - it will be initialized when
1326 // this chart will be first used
1327 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1328 NETDATA.error(402, that.library_name);
1329 error('chart library "' + that.library_name + '" is not found');
1332 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1333 NETDATA.error(403, that.library_name);
1334 error('chart library "' + that.library_name + '" is not enabled');
1338 that.library = NETDATA.chartLibraries[that.library_name];
1340 // milliseconds - the time the last refresh took
1341 this.refresh_dt_ms = 0;
1343 // if we need to report the rendering speed
1344 // find the element that needs to be updated
1345 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1347 if(refresh_dt_element_name !== null)
1348 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1350 this.refresh_dt_element = null;
1352 this.dimensions_visibility = new dimensionsVisibility(this);
1354 this._updating = false;
1356 // ============================================================================================================
1357 // PRIVATE FUNCTIONS
1359 var createDOM = function() {
1360 if(that.enabled === false) return;
1362 if(that.element_message !== null) that.element_message.innerHTML = '';
1363 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1364 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1366 that.element.innerHTML = '';
1368 that.element_message = document.createElement('div');
1369 that.element_message.className = ' netdata-message hidden';
1370 that.element.appendChild(that.element_message);
1372 that.element_chart = document.createElement('div');
1373 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1374 that.element.appendChild(that.element_chart);
1376 if(that.hasLegend() === true) {
1377 that.element.className = "netdata-container-with-legend";
1378 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1380 that.element_legend = document.createElement('div');
1381 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1382 that.element.appendChild(that.element_legend);
1385 that.element.className = "netdata-container";
1386 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1388 that.element_legend = null;
1390 that.element_legend_childs.series = null;
1392 if(typeof(that.width) === 'string')
1393 $(that.element).css('width', that.width);
1394 else if(typeof(that.width) === 'number')
1395 $(that.element).css('width', that.width + 'px');
1397 if(typeof(that.library.aspect_ratio) === 'undefined') {
1398 if(typeof(that.height) === 'string')
1399 $(that.element).css('height', that.height);
1400 else if(typeof(that.height) === 'number')
1401 $(that.element).css('height', that.height + 'px');
1404 var w = that.element.offsetWidth;
1405 if(w === null || w === 0) {
1406 // the div is hidden
1407 // this will resize the chart when next viewed
1408 that.tm.last_resized = 0;
1411 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1414 if(NETDATA.chartDefaults.min_width !== null)
1415 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1417 that.tm.last_dom_created = Date.now();
1423 * initialize state variables
1424 * destroy all (possibly) created state elements
1425 * create the basic DOM for a chart
1427 var init = function() {
1428 if(that.enabled === false) return;
1430 that.paused = false;
1431 that.selected = false;
1433 that.chart_created = false; // boolean - is the library.create() been called?
1434 that.updates_counter = 0; // numeric - the number of refreshes made so far
1435 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1436 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1439 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1440 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1441 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1443 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1444 last_updated: 0, // the timestamp the chart last updated with data
1445 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1447 // Used with NETDATA.globalPanAndZoom.seq
1448 last_visible_check: 0, // the time we last checked if it is visible
1449 last_resized: 0, // the time the chart was resized
1450 last_hidden: 0, // the time the chart was hidden
1451 last_unhidden: 0, // the time the chart was unhidden
1452 last_autorefreshed: 0 // the time the chart was last refreshed
1455 that.data = null; // the last data as downloaded from the netdata server
1456 that.data_url = 'invalid://'; // string - the last url used to update the chart
1457 that.data_points = 0; // number - the number of points returned from netdata
1458 that.data_after = 0; // milliseconds - the first timestamp of the data
1459 that.data_before = 0; // milliseconds - the last timestamp of the data
1460 that.data_update_every = 0; // milliseconds - the frequency to update the data
1462 that.tm.last_initialized = Date.now();
1465 that.setMode('auto');
1468 var maxMessageFontSize = function() {
1469 // normally we want a font size, as tall as the element
1470 var h = that.element_message.clientHeight;
1472 // but give it some air, 20% let's say, or 5 pixels min
1473 var lost = Math.max(h * 0.2, 5);
1476 // center the text, vertically
1477 var paddingTop = (lost - 5) / 2;
1479 // but check the width too
1480 // it should fit 10 characters in it
1481 var w = that.element_message.clientWidth / 10;
1483 paddingTop += (h - w) / 2;
1487 // and don't make it too huge
1488 // 5% of the screen size is good
1489 if(h > screen.height / 20) {
1490 paddingTop += (h - (screen.height / 20)) / 2;
1491 h = screen.height / 20;
1495 that.element_message.style.fontSize = h.toString() + 'px';
1496 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1499 var showMessage = function(msg) {
1500 that.element_message.className = 'netdata-message';
1501 that.element_message.innerHTML = msg;
1502 that.element_message.style.fontSize = 'x-small';
1503 that.element_message.style.paddingTop = '0px';
1504 that.___messageHidden___ = undefined;
1507 var showMessageIcon = function(icon) {
1508 that.element_message.innerHTML = icon;
1509 that.element_message.className = 'netdata-message icon';
1510 maxMessageFontSize();
1511 that.___messageHidden___ = undefined;
1514 var hideMessage = function() {
1515 if(typeof that.___messageHidden___ === 'undefined') {
1516 that.___messageHidden___ = true;
1517 that.element_message.className = 'netdata-message hidden';
1521 var showRendering = function() {
1523 if(that.chart !== null) {
1524 if(that.chart.chart_type === 'line')
1525 icon = '<i class="fa fa-line-chart"></i>';
1527 icon = '<i class="fa fa-area-chart"></i>';
1530 icon = '<i class="fa fa-area-chart"></i>';
1532 showMessageIcon(icon + ' netdata');
1535 var showLoading = function() {
1536 if(that.chart_created === false) {
1537 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1543 var isHidden = function() {
1544 if(typeof that.___chartIsHidden___ !== 'undefined')
1550 // hide the chart, when it is not visible - called from isVisible()
1551 var hideChart = function() {
1552 // hide it, if it is not already hidden
1553 if(isHidden() === true) return;
1555 if(that.chart_created === true) {
1556 if(NETDATA.options.current.destroy_on_hide === true) {
1557 // we should destroy it
1562 that.element_chart.style.display = 'none';
1563 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1564 that.tm.last_hidden = Date.now();
1567 // This works, but I not sure there are no corner cases somewhere
1568 // so it is commented - if the user has memory issues he can
1569 // set Destroy on Hide for all charts
1570 // that.data = null;
1574 that.___chartIsHidden___ = true;
1577 // unhide the chart, when it is visible - called from isVisible()
1578 var unhideChart = function() {
1579 if(isHidden() === false) return;
1581 that.___chartIsHidden___ = undefined;
1582 that.updates_since_last_unhide = 0;
1584 if(that.chart_created === false) {
1585 // we need to re-initialize it, to show our background
1586 // logo in bootstrap tabs, until the chart loads
1590 that.tm.last_unhidden = Date.now();
1591 that.element_chart.style.display = '';
1592 if(that.element_legend !== null) that.element_legend.style.display = '';
1598 var canBeRendered = function() {
1599 if(isHidden() === true || that.isVisible(true) === false)
1605 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1606 var callChartLibraryUpdateSafely = function(data) {
1609 if(canBeRendered() === false)
1612 if(NETDATA.options.debug.chart_errors === true)
1613 status = that.library.update(that, data);
1616 status = that.library.update(that, data);
1623 if(status === false) {
1624 error('chart failed to be updated as ' + that.library_name);
1631 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1632 var callChartLibraryCreateSafely = function(data) {
1635 if(canBeRendered() === false)
1638 if(NETDATA.options.debug.chart_errors === true)
1639 status = that.library.create(that, data);
1642 status = that.library.create(that, data);
1649 if(status === false) {
1650 error('chart failed to be created as ' + that.library_name);
1654 that.chart_created = true;
1655 that.updates_since_last_creation = 0;
1659 // ----------------------------------------------------------------------------------------------------------------
1662 // resizeChart() - private
1663 // to be called just before the chart library to make sure that
1664 // a properly sized dom is available
1665 var resizeChart = function() {
1666 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1667 if(that.chart_created === false) return;
1669 if(that.needsRecreation()) {
1672 else if(typeof that.library.resize === 'function') {
1673 that.library.resize(that);
1675 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1676 $(that.element_legend_childs.nano).nanoScroller();
1678 maxMessageFontSize();
1681 that.tm.last_resized = Date.now();
1685 // this is the actual chart resize algorithm
1687 // - resize the entire container
1688 // - update the internal states
1689 // - resize the chart as the div changes height
1690 // - update the scrollbar of the legend
1691 var resizeChartToHeight = function(h) {
1693 that.element.style.height = h;
1695 if(that.settings_id !== null)
1696 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1698 var now = Date.now();
1699 NETDATA.options.last_page_scroll = now;
1700 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1703 that.tm.last_resized = 0;
1707 this.resizeHandler = function(e) {
1710 if(typeof this.event_resize === 'undefined'
1711 || this.event_resize.chart_original_w === 'undefined'
1712 || this.event_resize.chart_original_h === 'undefined')
1713 this.event_resize = {
1714 chart_original_w: this.element.clientWidth,
1715 chart_original_h: this.element.clientHeight,
1719 if(e.type === 'touchstart') {
1720 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1721 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1724 this.event_resize.mouse_start_x = e.clientX;
1725 this.event_resize.mouse_start_y = e.clientY;
1728 this.event_resize.chart_start_w = this.element.clientWidth;
1729 this.event_resize.chart_start_h = this.element.clientHeight;
1730 this.event_resize.chart_last_w = this.element.clientWidth;
1731 this.event_resize.chart_last_h = this.element.clientHeight;
1733 var now = Date.now();
1734 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1735 // double click / double tap event
1737 // the optimal height of the chart
1738 // showing the entire legend
1739 var optimal = this.event_resize.chart_last_h
1740 + this.element_legend_childs.content.scrollHeight
1741 - this.element_legend_childs.content.clientHeight;
1743 // if we are not optimal, be optimal
1744 if(this.event_resize.chart_last_h != optimal)
1745 resizeChartToHeight(optimal.toString() + 'px');
1747 // else if we do not have the original height
1748 // reset to the original height
1749 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1750 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1753 this.event_resize.last = now;
1755 // process movement event
1756 document.onmousemove =
1757 document.ontouchmove =
1758 this.element_legend_childs.resize_handler.onmousemove =
1759 this.element_legend_childs.resize_handler.ontouchmove =
1764 case 'mousemove': y = e.clientY; break;
1765 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1769 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1771 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1772 resizeChartToHeight(newH.toString() + 'px');
1773 that.event_resize.chart_last_h = newH;
1778 // process end event
1779 document.onmouseup =
1780 document.ontouchend =
1781 this.element_legend_childs.resize_handler.onmouseup =
1782 this.element_legend_childs.resize_handler.ontouchend =
1784 // remove all the hooks
1785 document.onmouseup =
1786 document.onmousemove =
1787 document.ontouchmove =
1788 document.ontouchend =
1789 that.element_legend_childs.resize_handler.onmousemove =
1790 that.element_legend_childs.resize_handler.ontouchmove =
1791 that.element_legend_childs.resize_handler.onmouseout =
1792 that.element_legend_childs.resize_handler.onmouseup =
1793 that.element_legend_childs.resize_handler.ontouchend =
1796 // allow auto-refreshes
1797 NETDATA.options.auto_refresher_stop_until = 0;
1803 var noDataToShow = function() {
1804 showMessageIcon('<i class="fa fa-warning"></i> empty');
1805 that.legendUpdateDOM();
1806 that.tm.last_autorefreshed = Date.now();
1807 // that.data_update_every = 30 * 1000;
1808 //that.element_chart.style.display = 'none';
1809 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1810 //that.___chartIsHidden___ = true;
1813 // ============================================================================================================
1816 this.error = function(msg) {
1820 this.setMode = function(m) {
1821 if(this.current !== null && this.current.name === m) return;
1824 this.current = this.auto;
1825 else if(m === 'pan')
1826 this.current = this.pan;
1827 else if(m === 'zoom')
1828 this.current = this.zoom;
1830 this.current = this.auto;
1832 this.current.force_update_at = 0;
1833 this.current.force_before_ms = null;
1834 this.current.force_after_ms = null;
1836 this.tm.last_mode_switch = Date.now();
1839 // ----------------------------------------------------------------------------------------------------------------
1840 // global selection sync
1842 // prevent to global selection sync for some time
1843 this.globalSelectionSyncDelay = function(ms) {
1844 if(NETDATA.options.current.sync_selection === false)
1847 if(typeof ms === 'number')
1848 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1850 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1853 // can we globally apply selection sync?
1854 this.globalSelectionSyncAbility = function() {
1855 if(NETDATA.options.current.sync_selection === false)
1858 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1864 this.globalSelectionSyncIsMaster = function() {
1865 if(NETDATA.globalSelectionSync.state === this)
1871 // this chart is the master of the global selection sync
1872 this.globalSelectionSyncBeMaster = function() {
1874 if(this.globalSelectionSyncIsMaster()) {
1875 if(this.debug === true)
1876 this.log('sync: I am the master already.');
1881 if(NETDATA.globalSelectionSync.state) {
1882 if(this.debug === true)
1883 this.log('sync: I am not the sync master. Resetting global sync.');
1885 this.globalSelectionSyncStop();
1888 // become the master
1889 if(this.debug === true)
1890 this.log('sync: becoming sync master.');
1892 this.selected = true;
1893 NETDATA.globalSelectionSync.state = this;
1895 // find the all slaves
1896 var targets = NETDATA.options.targets;
1897 var len = targets.length;
1902 if(this.debug === true)
1903 st.log('sync: not adding me to sync');
1905 else if(st.globalSelectionSyncIsEligible()) {
1906 if(this.debug === true)
1907 st.log('sync: adding to sync as slave');
1909 st.globalSelectionSyncBeSlave();
1913 // this.globalSelectionSyncDelay(100);
1916 // can the chart participate to the global selection sync as a slave?
1917 this.globalSelectionSyncIsEligible = function() {
1918 if(this.enabled === true
1919 && this.library !== null
1920 && typeof this.library.setSelection === 'function'
1921 && this.isVisible() === true
1922 && this.chart_created === true)
1928 // this chart becomes a slave of the global selection sync
1929 this.globalSelectionSyncBeSlave = function() {
1930 if(NETDATA.globalSelectionSync.state !== this)
1931 NETDATA.globalSelectionSync.slaves.push(this);
1934 // sync all the visible charts to the given time
1935 // this is to be called from the chart libraries
1936 this.globalSelectionSync = function(t) {
1937 if(this.globalSelectionSyncAbility() === false) {
1938 if(this.debug === true)
1939 this.log('sync: cannot sync (yet?).');
1944 if(this.globalSelectionSyncIsMaster() === false) {
1945 if(this.debug === true)
1946 this.log('sync: trying to be sync master.');
1948 this.globalSelectionSyncBeMaster();
1950 if(this.globalSelectionSyncAbility() === false) {
1951 if(this.debug === true)
1952 this.log('sync: cannot sync (yet?).');
1958 NETDATA.globalSelectionSync.last_t = t;
1959 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1964 // stop syncing all charts to the given time
1965 this.globalSelectionSyncStop = function() {
1966 if(NETDATA.globalSelectionSync.slaves.length) {
1967 if(this.debug === true)
1968 this.log('sync: cleaning up...');
1970 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1972 if(that.debug === true)
1973 st.log('sync: not adding me to sync stop');
1976 if(that.debug === true)
1977 st.log('sync: removed slave from sync');
1979 st.clearSelection();
1983 NETDATA.globalSelectionSync.last_t = 0;
1984 NETDATA.globalSelectionSync.slaves = [];
1985 NETDATA.globalSelectionSync.state = null;
1988 this.clearSelection();
1991 this.setSelection = function(t) {
1992 if(typeof this.library.setSelection === 'function') {
1993 if(this.library.setSelection(this, t) === true)
1994 this.selected = true;
1996 this.selected = false;
1998 else this.selected = true;
2000 if(this.selected === true && this.debug === true)
2001 this.log('selection set to ' + t.toString());
2003 return this.selected;
2006 this.clearSelection = function() {
2007 if(this.selected === true) {
2008 if(typeof this.library.clearSelection === 'function') {
2009 if(this.library.clearSelection(this) === true)
2010 this.selected = false;
2012 this.selected = true;
2014 else this.selected = false;
2016 if(this.selected === false && this.debug === true)
2017 this.log('selection cleared');
2022 return this.selected;
2025 // find if a timestamp (ms) is shown in the current chart
2026 this.timeIsVisible = function(t) {
2027 if(t >= this.data_after && t <= this.data_before)
2032 this.calculateRowForTime = function(t) {
2033 if(this.timeIsVisible(t) === false) return -1;
2034 return Math.floor((t - this.data_after) / this.data_update_every);
2037 // ----------------------------------------------------------------------------------------------------------------
2040 this.log = function(msg) {
2041 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2044 this.pauseChart = function() {
2045 if(this.paused === false) {
2046 if(this.debug === true)
2047 this.log('pauseChart()');
2053 this.unpauseChart = function() {
2054 if(this.paused === true) {
2055 if(this.debug === true)
2056 this.log('unpauseChart()');
2058 this.paused = false;
2062 this.resetChart = function(dont_clear_master, dont_update) {
2063 if(this.debug === true)
2064 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2066 if(typeof dont_clear_master === 'undefined')
2067 dont_clear_master = false;
2069 if(typeof dont_update === 'undefined')
2070 dont_update = false;
2072 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2073 if(this.debug === true)
2074 this.log('resetChart() diverting to clearMaster().');
2075 // this will call us back with master === true
2076 NETDATA.globalPanAndZoom.clearMaster();
2080 this.clearSelection();
2082 this.tm.pan_and_zoom_seq = 0;
2084 this.setMode('auto');
2085 this.current.force_update_at = 0;
2086 this.current.force_before_ms = null;
2087 this.current.force_after_ms = null;
2088 this.tm.last_autorefreshed = 0;
2089 this.paused = false;
2090 this.selected = false;
2091 this.enabled = true;
2092 // this.debug = false;
2094 // do not update the chart here
2095 // or the chart will flip-flop when it is the master
2096 // of a selection sync and another chart becomes
2099 if(dont_update !== true && this.isVisible() === true) {
2104 this.updateChartPanOrZoom = function(after, before) {
2105 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2108 if(this.debug === true)
2111 if(before < after) {
2112 if(this.debug === true)
2113 this.log(logme + 'flipped parameters, rejecting it.');
2118 if(typeof this.fixed_min_duration === 'undefined')
2119 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2121 var min_duration = this.fixed_min_duration;
2122 var current_duration = Math.round(this.view_before - this.view_after);
2124 // round the numbers
2125 after = Math.round(after);
2126 before = Math.round(before);
2128 // align them to update_every
2129 // stretching them further away
2130 after -= after % this.data_update_every;
2131 before += this.data_update_every - (before % this.data_update_every);
2133 // the final wanted duration
2134 var wanted_duration = before - after;
2136 // to allow panning, accept just a point below our minimum
2137 if((current_duration - this.data_update_every) < min_duration)
2138 min_duration = current_duration - this.data_update_every;
2140 // we do it, but we adjust to minimum size and return false
2141 // when the wanted size is below the current and the minimum
2143 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2144 if(this.debug === true)
2145 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2147 min_duration = this.fixed_min_duration;
2149 var dt = (min_duration - wanted_duration) / 2;
2152 wanted_duration = before - after;
2156 var tolerance = this.data_update_every * 2;
2157 var movement = Math.abs(before - this.view_before);
2159 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2160 if(this.debug === true)
2161 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);
2165 if(this.current.name === 'auto') {
2166 this.log(logme + 'caller called me with mode: ' + this.current.name);
2167 this.setMode('pan');
2170 if(this.debug === true)
2171 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);
2173 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2174 this.current.force_after_ms = after;
2175 this.current.force_before_ms = before;
2176 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2180 this.legendFormatValue = function(value) {
2181 if(value === null || value === 'undefined') return '-';
2182 if(typeof value !== 'number') return value;
2184 if(this.value_decimal_detail !== -1)
2185 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2187 var abs = Math.abs(value);
2188 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2189 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2190 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2191 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2192 return (Math.round(value * 10000) / 10000).toLocaleString();
2195 this.legendSetLabelValue = function(label, value) {
2196 var series = this.element_legend_childs.series[label];
2197 if(typeof series === 'undefined') return;
2198 if(series.value === null && series.user === null) return;
2201 // this slows down firefox and edge significantly
2202 // since it requires to use innerHTML(), instead of innerText()
2204 // if the value has not changed, skip DOM update
2205 //if(series.last === value) return;
2208 if(typeof value === 'number') {
2209 var v = Math.abs(value);
2210 s = r = this.legendFormatValue(value);
2212 if(typeof series.last === 'number') {
2213 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2214 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2215 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2217 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2227 series.last = value;
2231 var s = this.legendFormatValue(value);
2233 // caching: do not update the update to show the same value again
2234 if(s === series.last_shown_value) return;
2235 series.last_shown_value = s;
2237 if(series.value !== null) series.value.innerText = s;
2238 if(series.user !== null) series.user.innerText = s;
2241 this.__legendSetDateString = function(date) {
2242 if(date !== this.__last_shown_legend_date) {
2243 this.element_legend_childs.title_date.innerText = date;
2244 this.__last_shown_legend_date = date;
2248 this.__legendSetTimeString = function(time) {
2249 if(time !== this.__last_shown_legend_time) {
2250 this.element_legend_childs.title_time.innerText = time;
2251 this.__last_shown_legend_time = time;
2255 this.__legendSetUnitsString = function(units) {
2256 if(units !== this.__last_shown_legend_units) {
2257 this.element_legend_childs.title_units.innerText = units;
2258 this.__last_shown_legend_units = units;
2262 this.legendSetDate = function(ms) {
2263 if(typeof ms !== 'number') {
2264 this.legendShowUndefined();
2268 var d = new Date(ms);
2270 if(this.element_legend_childs.title_date)
2271 this.__legendSetDateString(d.toLocaleDateString());
2273 if(this.element_legend_childs.title_time)
2274 this.__legendSetTimeString(d.toLocaleTimeString());
2276 if(this.element_legend_childs.title_units)
2277 this.__legendSetUnitsString(this.units)
2280 this.legendShowUndefined = function() {
2281 if(this.element_legend_childs.title_date)
2282 this.__legendSetDateString(' ');
2284 if(this.element_legend_childs.title_time)
2285 this.__legendSetTimeString(this.chart.name);
2287 if(this.element_legend_childs.title_units)
2288 this.__legendSetUnitsString(' ')
2290 if(this.data && this.element_legend_childs.series !== null) {
2291 var labels = this.data.dimension_names;
2292 var i = labels.length;
2294 var label = labels[i];
2296 if(typeof label === 'undefined') continue;
2297 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2298 this.legendSetLabelValue(label, null);
2303 this.legendShowLatestValues = function() {
2304 if(this.chart === null) return;
2305 if(this.selected) return;
2307 if(this.data === null || this.element_legend_childs.series === null) {
2308 this.legendShowUndefined();
2312 var show_undefined = true;
2313 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2314 show_undefined = false;
2316 if(show_undefined) {
2317 this.legendShowUndefined();
2321 this.legendSetDate(this.view_before);
2323 var labels = this.data.dimension_names;
2324 var i = labels.length;
2326 var label = labels[i];
2328 if(typeof label === 'undefined') continue;
2329 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2332 this.legendSetLabelValue(label, null);
2334 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2338 this.legendReset = function() {
2339 this.legendShowLatestValues();
2342 // this should be called just ONCE per dimension per chart
2343 this._chartDimensionColor = function(label) {
2344 if(this.colors === null) this.chartColors();
2346 if(typeof this.colors_assigned[label] === 'undefined') {
2347 if(this.colors_available.length === 0) {
2348 var len = NETDATA.themes.current.colors.length;
2350 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2353 this.colors_assigned[label] = this.colors_available.shift();
2355 if(this.debug === true)
2356 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2359 if(this.debug === true)
2360 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2363 this.colors.push(this.colors_assigned[label]);
2364 return this.colors_assigned[label];
2367 this.chartColors = function() {
2368 if(this.colors !== null) return this.colors;
2370 this.colors = new Array();
2371 this.colors_available = new Array();
2373 // add the standard colors
2374 var len = NETDATA.themes.current.colors.length;
2376 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2378 // add the user supplied colors
2379 var c = $(this.element).data('colors');
2380 // this.log('read colors: ' + c);
2381 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2382 if(typeof c !== 'string') {
2383 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2393 this.colors_available.unshift(c[len]);
2394 // this.log('adding color: ' + c[len]);
2403 this.legendUpdateDOM = function() {
2406 // check that the legend DOM is up to date for the downloaded dimensions
2407 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2408 // this.log('the legend does not have any series - requesting legend update');
2411 else if(this.data === null) {
2412 // this.log('the chart does not have any data - requesting legend update');
2415 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2419 var labels = this.data.dimension_names.toString();
2420 if(labels !== this.element_legend_childs.series.labels_key) {
2423 if(this.debug === true)
2424 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2428 if(needed === false) {
2429 // make sure colors available
2432 // do we have to update the current values?
2433 // we do this, only when the visible chart is current
2434 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2435 if(this.debug === true)
2436 this.log('chart is in latest position... updating values on legend...');
2438 //var labels = this.data.dimension_names;
2439 //var i = labels.length;
2441 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2445 if(this.colors === null) {
2446 // this is the first time we update the chart
2447 // let's assign colors to all dimensions
2448 if(this.library.track_colors() === true)
2449 for(var dim in this.chart.dimensions)
2450 this._chartDimensionColor(this.chart.dimensions[dim].name);
2452 // we will re-generate the colors for the chart
2453 // based on the selected dimensions
2456 if(this.debug === true)
2457 this.log('updating Legend DOM');
2459 // mark all dimensions as invalid
2460 this.dimensions_visibility.invalidateAll();
2462 var genLabel = function(state, parent, dim, name, count) {
2463 var color = state._chartDimensionColor(name);
2465 var user_element = null;
2466 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2467 if(user_id === null)
2468 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2469 if(user_id !== null) {
2470 user_element = document.getElementById(user_id) || null;
2471 if (user_element === null)
2472 state.log('Cannot find element with id: ' + user_id);
2475 state.element_legend_childs.series[name] = {
2476 name: document.createElement('span'),
2477 value: document.createElement('span'),
2480 last_shown_value: null
2483 var label = state.element_legend_childs.series[name];
2485 // create the dimension visibility tracking for this label
2486 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2488 var rgb = NETDATA.colorHex2Rgb(color);
2489 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2490 + state.chart.chart_type
2491 + '" style="background-color: '
2492 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2493 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2495 var text = document.createTextNode(' ' + name);
2496 label.name.appendChild(text);
2499 parent.appendChild(document.createElement('br'));
2501 parent.appendChild(label.name);
2502 parent.appendChild(label.value);
2505 var content = document.createElement('div');
2507 if(this.hasLegend()) {
2508 this.element_legend_childs = {
2510 resize_handler: document.createElement('div'),
2511 toolbox: document.createElement('div'),
2512 toolbox_left: document.createElement('div'),
2513 toolbox_right: document.createElement('div'),
2514 toolbox_reset: document.createElement('div'),
2515 toolbox_zoomin: document.createElement('div'),
2516 toolbox_zoomout: document.createElement('div'),
2517 toolbox_volume: document.createElement('div'),
2518 title_date: document.createElement('span'),
2519 title_time: document.createElement('span'),
2520 title_units: document.createElement('span'),
2521 nano: document.createElement('div'),
2523 paneClass: 'netdata-legend-series-pane',
2524 sliderClass: 'netdata-legend-series-slider',
2525 contentClass: 'netdata-legend-series-content',
2526 enabledClass: '__enabled',
2527 flashedClass: '__flashed',
2528 activeClass: '__active',
2530 alwaysVisible: true,
2536 this.element_legend.innerHTML = '';
2538 if(this.library.toolboxPanAndZoom !== null) {
2540 function get_pan_and_zoom_step(event) {
2542 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2544 else if (event.shiftKey)
2545 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2547 else if (event.altKey)
2548 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2551 return NETDATA.options.current.pan_and_zoom_factor;
2554 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2555 this.element.appendChild(this.element_legend_childs.toolbox);
2557 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2558 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2559 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2560 this.element_legend_childs.toolbox_left.onclick = function(e) {
2563 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2564 var before = that.view_before - step;
2565 var after = that.view_after - step;
2566 if(after >= that.netdata_first)
2567 that.library.toolboxPanAndZoom(that, after, before);
2569 if(NETDATA.options.current.show_help === true)
2570 $(this.element_legend_childs.toolbox_left).popover({
2575 placement: 'bottom',
2576 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2578 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>'
2582 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2583 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2584 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2585 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2587 NETDATA.resetAllCharts(that);
2589 if(NETDATA.options.current.show_help === true)
2590 $(this.element_legend_childs.toolbox_reset).popover({
2595 placement: 'bottom',
2596 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2597 title: 'Chart Reset',
2598 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>'
2601 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2602 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2603 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2604 this.element_legend_childs.toolbox_right.onclick = function(e) {
2606 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2607 var before = that.view_before + step;
2608 var after = that.view_after + step;
2609 if(before <= that.netdata_last)
2610 that.library.toolboxPanAndZoom(that, after, before);
2612 if(NETDATA.options.current.show_help === true)
2613 $(this.element_legend_childs.toolbox_right).popover({
2618 placement: 'bottom',
2619 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2621 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>'
2625 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2626 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2627 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2628 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2630 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2631 var before = that.view_before - dt;
2632 var after = that.view_after + dt;
2633 that.library.toolboxPanAndZoom(that, after, before);
2635 if(NETDATA.options.current.show_help === true)
2636 $(this.element_legend_childs.toolbox_zoomin).popover({
2641 placement: 'bottom',
2642 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2643 title: 'Chart Zoom In',
2644 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>'
2647 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2648 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2649 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2650 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2652 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);
2653 var before = that.view_before + dt;
2654 var after = that.view_after - dt;
2656 that.library.toolboxPanAndZoom(that, after, before);
2658 if(NETDATA.options.current.show_help === true)
2659 $(this.element_legend_childs.toolbox_zoomout).popover({
2664 placement: 'bottom',
2665 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2666 title: 'Chart Zoom Out',
2667 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>'
2670 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2671 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2672 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2673 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2674 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2675 //e.preventDefault();
2676 //alert('clicked toolbox_volume on ' + that.id);
2680 this.element_legend_childs.toolbox = null;
2681 this.element_legend_childs.toolbox_left = null;
2682 this.element_legend_childs.toolbox_reset = null;
2683 this.element_legend_childs.toolbox_right = null;
2684 this.element_legend_childs.toolbox_zoomin = null;
2685 this.element_legend_childs.toolbox_zoomout = null;
2686 this.element_legend_childs.toolbox_volume = null;
2689 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2690 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2691 this.element.appendChild(this.element_legend_childs.resize_handler);
2692 if(NETDATA.options.current.show_help === true)
2693 $(this.element_legend_childs.resize_handler).popover({
2698 placement: 'bottom',
2699 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2700 title: 'Chart Resize',
2701 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>'
2705 this.element_legend_childs.resize_handler.onmousedown =
2707 that.resizeHandler(e);
2711 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2712 that.resizeHandler(e);
2715 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2716 this.element_legend.appendChild(this.element_legend_childs.title_date);
2718 this.element_legend.appendChild(document.createElement('br'));
2720 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2721 this.element_legend.appendChild(this.element_legend_childs.title_time);
2723 this.element_legend.appendChild(document.createElement('br'));
2725 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2726 this.element_legend.appendChild(this.element_legend_childs.title_units);
2728 this.element_legend.appendChild(document.createElement('br'));
2730 this.element_legend_childs.nano.className = 'netdata-legend-series';
2731 this.element_legend.appendChild(this.element_legend_childs.nano);
2733 content.className = 'netdata-legend-series-content';
2734 this.element_legend_childs.nano.appendChild(content);
2736 if(NETDATA.options.current.show_help === true)
2737 $(content).popover({
2742 placement: 'bottom',
2743 title: 'Chart Legend',
2744 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2745 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>'
2749 this.element_legend_childs = {
2751 resize_handler: null,
2754 toolbox_right: null,
2755 toolbox_reset: null,
2756 toolbox_zoomin: null,
2757 toolbox_zoomout: null,
2758 toolbox_volume: null,
2769 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2770 if(this.debug === true)
2771 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2773 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2774 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2778 var tmp = new Array();
2779 for(var dim in this.chart.dimensions) {
2780 tmp.push(this.chart.dimensions[dim].name);
2781 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2783 this.element_legend_childs.series.labels_key = tmp.toString();
2784 if(this.debug === true)
2785 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2788 // create a hidden div to be used for hidding
2789 // the original legend of the chart library
2790 var el = document.createElement('div');
2791 if(this.element_legend !== null)
2792 this.element_legend.appendChild(el);
2793 el.style.display = 'none';
2795 this.element_legend_childs.hidden = document.createElement('div');
2796 el.appendChild(this.element_legend_childs.hidden);
2798 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2799 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2801 this.legendShowLatestValues();
2804 this.hasLegend = function() {
2805 if(typeof this.___hasLegendCache___ !== 'undefined')
2806 return this.___hasLegendCache___;
2809 if(this.library && this.library.legend(this) === 'right-side') {
2810 var legend = $(this.element).data('legend') || 'yes';
2811 if(legend === 'yes') leg = true;
2814 this.___hasLegendCache___ = leg;
2818 this.legendWidth = function() {
2819 return (this.hasLegend())?140:0;
2822 this.legendHeight = function() {
2823 return $(this.element).height();
2826 this.chartWidth = function() {
2827 return $(this.element).width() - this.legendWidth();
2830 this.chartHeight = function() {
2831 return $(this.element).height();
2834 this.chartPixelsPerPoint = function() {
2835 // force an options provided detail
2836 var px = this.pixels_per_point;
2838 if(this.library && px < this.library.pixels_per_point(this))
2839 px = this.library.pixels_per_point(this);
2841 if(px < NETDATA.options.current.pixels_per_point)
2842 px = NETDATA.options.current.pixels_per_point;
2847 this.needsRecreation = function() {
2849 this.chart_created === true
2851 && this.library.autoresize() === false
2852 && this.tm.last_resized < NETDATA.options.last_resized
2856 this.chartURL = function() {
2857 var after, before, points_multiplier = 1;
2858 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2859 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2861 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2862 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2863 this.view_after = after * 1000;
2864 this.view_before = before * 1000;
2866 this.requested_padding = null;
2867 points_multiplier = 1;
2869 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2870 this.tm.pan_and_zoom_seq = 0;
2872 before = Math.round(this.current.force_before_ms / 1000);
2873 after = Math.round(this.current.force_after_ms / 1000);
2874 this.view_after = after * 1000;
2875 this.view_before = before * 1000;
2877 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2878 this.requested_padding = Math.round((before - after) / 2);
2879 after -= this.requested_padding;
2880 before += this.requested_padding;
2881 this.requested_padding *= 1000;
2882 points_multiplier = 2;
2885 this.current.force_before_ms = null;
2886 this.current.force_after_ms = null;
2889 this.tm.pan_and_zoom_seq = 0;
2891 before = this.before;
2893 this.view_after = after * 1000;
2894 this.view_before = before * 1000;
2896 this.requested_padding = null;
2897 points_multiplier = 1;
2900 this.requested_after = after * 1000;
2901 this.requested_before = before * 1000;
2903 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2905 // build the data URL
2906 this.data_url = this.host + this.chart.data_url;
2907 this.data_url += "&format=" + this.library.format();
2908 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2909 this.data_url += "&group=" + this.method;
2911 if(this.override_options !== null)
2912 this.data_url += "&options=" + this.override_options.toString();
2914 this.data_url += "&options=" + this.library.options(this);
2916 this.data_url += '|jsonwrap';
2918 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2919 this.data_url += '|nonzero';
2921 if(this.append_options !== null)
2922 this.data_url += '|' + this.append_options.toString();
2925 this.data_url += "&after=" + after.toString();
2928 this.data_url += "&before=" + before.toString();
2931 this.data_url += "&dimensions=" + this.dimensions;
2933 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2934 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2937 this.redrawChart = function() {
2938 if(this.data !== null)
2939 this.updateChartWithData(this.data);
2942 this.updateChartWithData = function(data) {
2943 if(this.debug === true)
2944 this.log('updateChartWithData() called.');
2946 // this may force the chart to be re-created
2950 this.updates_counter++;
2951 this.updates_since_last_unhide++;
2952 this.updates_since_last_creation++;
2954 var started = Date.now();
2956 // if the result is JSON, find the latest update-every
2957 this.data_update_every = data.view_update_every * 1000;
2958 this.data_after = data.after * 1000;
2959 this.data_before = data.before * 1000;
2960 this.netdata_first = data.first_entry * 1000;
2961 this.netdata_last = data.last_entry * 1000;
2962 this.data_points = data.points;
2965 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2966 if(this.view_after < this.data_after) {
2967 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2968 this.view_after = this.data_after;
2971 if(this.view_before > this.data_before) {
2972 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2973 this.view_before = this.data_before;
2977 this.view_after = this.data_after;
2978 this.view_before = this.data_before;
2981 if(this.debug === true) {
2982 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2984 if(this.current.force_after_ms)
2985 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2987 this.log('STATUS: forced : unset');
2989 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2990 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2991 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2992 this.log('STATUS: points : ' + (this.data_points).toString());
2995 if(this.data_points === 0) {
3000 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3001 if(this.debug === true)
3002 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3004 this.chart_created = false;
3007 // check and update the legend
3008 this.legendUpdateDOM();
3010 if(this.chart_created === true
3011 && typeof this.library.update === 'function') {
3013 if(this.debug === true)
3014 this.log('updating chart...');
3016 if(callChartLibraryUpdateSafely(data) === false)
3020 if(this.debug === true)
3021 this.log('creating chart...');
3023 if(callChartLibraryCreateSafely(data) === false)
3027 this.legendShowLatestValues();
3028 if(this.selected === true)
3029 NETDATA.globalSelectionSync.stop();
3031 // update the performance counters
3032 var now = Date.now();
3033 this.tm.last_updated = now;
3035 // don't update last_autorefreshed if this chart is
3036 // forced to be updated with global PanAndZoom
3037 if(NETDATA.globalPanAndZoom.isActive())
3038 this.tm.last_autorefreshed = 0;
3040 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3041 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3043 this.tm.last_autorefreshed = now;
3046 this.refresh_dt_ms = now - started;
3047 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3049 if(this.refresh_dt_element !== null)
3050 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3053 this.updateChart = function(callback) {
3054 if(this.debug === true)
3055 this.log('updateChart() called.');
3057 if(this._updating === true) {
3058 if(this.debug === true)
3059 this.log('I am already updating...');
3061 if(typeof callback === 'function') callback();
3065 // due to late initialization of charts and libraries
3066 // we need to check this too
3067 if(this.enabled === false) {
3068 if(this.debug === true)
3069 this.log('I am not enabled');
3071 if(typeof callback === 'function') callback();
3075 if(canBeRendered() === false) {
3076 if(typeof callback === 'function') callback();
3080 if(this.chart === null) {
3081 this.getChart(function() { that.updateChart(callback); });
3085 if(this.library.initialized === false) {
3086 if(this.library.enabled === true) {
3087 this.library.initialize(function() { that.updateChart(callback); });
3091 error('chart library "' + this.library_name + '" is not available.');
3092 if(typeof callback === 'function') callback();
3097 this.clearSelection();
3100 if(this.debug === true)
3101 this.log('updating from ' + this.data_url);
3103 NETDATA.statistics.refreshes_total++;
3104 NETDATA.statistics.refreshes_active++;
3106 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3107 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3109 this._updating = true;
3111 this.xhr = $.ajax( {
3116 'Cache-Control': 'no-cache, no-store',
3117 'Pragma': 'no-cache'
3119 xhrFields: { withCredentials: true } // required for the cookie
3121 .done(function(data) {
3122 that.xhr = undefined;
3123 that.retries_on_data_failures = 0;
3125 if(that.debug === true)
3126 that.log('data received. updating chart.');
3128 that.updateChartWithData(data);
3130 .fail(function(msg) {
3131 that.xhr = undefined;
3133 if(msg.statusText !== 'abort') {
3134 that.retries_on_data_failures++;
3135 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3136 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3137 that.retries_on_data_failures = 0;
3138 error('data download failed for url: ' + that.data_url);
3141 that.tm.last_autorefreshed = Date.now();
3142 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3146 .always(function() {
3147 that.xhr = undefined;
3149 NETDATA.statistics.refreshes_active--;
3150 that._updating = false;
3151 if(typeof callback === 'function') callback();
3157 this.isVisible = function(nocache) {
3158 if(typeof nocache === 'undefined')
3161 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3163 // caching - we do not evaluate the charts visibility
3164 // if the page has not been scrolled since the last check
3165 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3166 return this.___isVisible___;
3168 this.tm.last_visible_check = Date.now();
3170 var wh = window.innerHeight;
3171 var x = this.element.getBoundingClientRect();
3175 if(x.width === 0 || x.height === 0) {
3177 this.___isVisible___ = false;
3178 return this.___isVisible___;
3181 if(x.top < 0 && -x.top > x.height) {
3182 // the chart is entirely above
3183 ret = -x.top - x.height;
3185 else if(x.top > wh) {
3186 // the chart is entirely below
3190 if(ret > tolerance) {
3191 // the chart is too far
3194 this.___isVisible___ = false;
3195 return this.___isVisible___;
3198 // the chart is inside or very close
3201 this.___isVisible___ = true;
3202 return this.___isVisible___;
3206 this.isAutoRefreshable = function() {
3207 return (this.current.autorefresh);
3210 this.canBeAutoRefreshed = function() {
3211 var now = Date.now();
3213 if(this.running === true) {
3214 if(this.debug === true)
3215 this.log('I am already running');
3220 if(this.enabled === false) {
3221 if(this.debug === true)
3222 this.log('I am not enabled');
3227 if(this.library === null || this.library.enabled === false) {
3228 error('charting library "' + this.library_name + '" is not available');
3229 if(this.debug === true)
3230 this.log('My chart library ' + this.library_name + ' is not available');
3235 if(this.isVisible() === false) {
3236 if(NETDATA.options.debug.visibility === true || this.debug === true)
3237 this.log('I am not visible');
3242 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3243 if(this.debug === true)
3244 this.log('timed force update detected - allowing this update');
3246 this.current.force_update_at = 0;
3250 if(this.isAutoRefreshable() === true) {
3251 // allow the first update, even if the page is not visible
3252 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3253 if(NETDATA.options.debug.focus === true || this.debug === true)
3254 this.log('canBeAutoRefreshed(): page does not have focus');
3259 if(this.needsRecreation() === true) {
3260 if(this.debug === true)
3261 this.log('canBeAutoRefreshed(): needs re-creation.');
3266 // options valid only for autoRefresh()
3267 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3268 if(NETDATA.globalPanAndZoom.isActive()) {
3269 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3270 if(this.debug === true)
3271 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3276 if(this.debug === true)
3277 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3283 if(this.selected === true) {
3284 if(this.debug === true)
3285 this.log('canBeAutoRefreshed(): I have a selection in place.');
3290 if(this.paused === true) {
3291 if(this.debug === true)
3292 this.log('canBeAutoRefreshed(): I am paused.');
3297 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3298 if(this.debug === true)
3299 this.log('canBeAutoRefreshed(): It is time to update me.');
3309 this.autoRefresh = function(callback) {
3310 if(this.canBeAutoRefreshed() === true && this.running === false) {
3313 state.running = true;
3314 state.updateChart(function() {
3315 state.running = false;
3317 if(typeof callback !== 'undefined')
3322 if(typeof callback !== 'undefined')
3327 this._defaultsFromDownloadedChart = function(chart) {
3329 this.chart_url = chart.url;
3330 this.data_update_every = chart.update_every * 1000;
3331 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3332 this.tm.last_info_downloaded = Date.now();
3334 if(this.title === null)
3335 this.title = chart.title;
3337 if(this.units === null)
3338 this.units = chart.units;
3341 // fetch the chart description from the netdata server
3342 this.getChart = function(callback) {
3343 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3345 this._defaultsFromDownloadedChart(this.chart);
3346 if(typeof callback === 'function') callback();
3349 this.chart_url = "/api/v1/chart?chart=" + this.id;
3351 if(this.debug === true)
3352 this.log('downloading ' + this.chart_url);
3355 url: this.host + this.chart_url,
3358 xhrFields: { withCredentials: true } // required for the cookie
3360 .done(function(chart) {
3361 chart.url = that.chart_url;
3362 that._defaultsFromDownloadedChart(chart);
3363 NETDATA.chartRegistry.add(that.host, that.id, chart);
3366 NETDATA.error(404, that.chart_url);
3367 error('chart not found on url "' + that.chart_url + '"');
3369 .always(function() {
3370 if(typeof callback === 'function') callback();
3375 // ============================================================================================================
3381 NETDATA.resetAllCharts = function(state) {
3382 // first clear the global selection sync
3383 // to make sure no chart is in selected state
3384 state.globalSelectionSyncStop();
3386 // there are 2 possibilities here
3387 // a. state is the global Pan and Zoom master
3388 // b. state is not the global Pan and Zoom master
3390 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3393 // clear the global Pan and Zoom
3394 // this will also refresh the master
3395 // and unblock any charts currently mirroring the master
3396 NETDATA.globalPanAndZoom.clearMaster();
3398 // if we were not the master, reset our status too
3399 // this is required because most probably the mouse
3400 // is over this chart, blocking it from auto-refreshing
3401 if(master === false && (state.paused === true || state.selected === true))
3405 // get or create a chart state, given a DOM element
3406 NETDATA.chartState = function(element) {
3407 var state = $(element).data('netdata-state-object') || null;
3408 if(state === null) {
3409 state = new chartState(element);
3410 $(element).data('netdata-state-object', state);
3415 // ----------------------------------------------------------------------------------------------------------------
3416 // Library functions
3418 // Load a script without jquery
3419 // This is used to load jquery - after it is loaded, we use jquery
3420 NETDATA._loadjQuery = function(callback) {
3421 if(typeof jQuery === 'undefined') {
3422 if(NETDATA.options.debug.main_loop === true)
3423 console.log('loading ' + NETDATA.jQuery);
3425 var script = document.createElement('script');
3426 script.type = 'text/javascript';
3427 script.async = true;
3428 script.src = NETDATA.jQuery;
3430 // script.onabort = onError;
3431 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3432 if(typeof callback === "function")
3433 script.onload = callback;
3435 var s = document.getElementsByTagName('script')[0];
3436 s.parentNode.insertBefore(script, s);
3438 else if(typeof callback === "function")
3442 NETDATA._loadCSS = function(filename) {
3443 // don't use jQuery here
3444 // styles are loaded before jQuery
3445 // to eliminate showing an unstyled page to the user
3447 var fileref = document.createElement("link");
3448 fileref.setAttribute("rel", "stylesheet");
3449 fileref.setAttribute("type", "text/css");
3450 fileref.setAttribute("href", filename);
3452 if (typeof fileref !== 'undefined')
3453 document.getElementsByTagName("head")[0].appendChild(fileref);
3456 NETDATA.colorHex2Rgb = function(hex) {
3457 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3458 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3459 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3460 return r + r + g + g + b + b;
3463 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3465 r: parseInt(result[1], 16),
3466 g: parseInt(result[2], 16),
3467 b: parseInt(result[3], 16)
3471 NETDATA.colorLuminance = function(hex, lum) {
3472 // validate hex string
3473 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3475 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3479 // convert to decimal and change luminosity
3480 var rgb = "#", c, i;
3481 for (i = 0; i < 3; i++) {
3482 c = parseInt(hex.substr(i*2,2), 16);
3483 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3484 rgb += ("00"+c).substr(c.length);
3490 NETDATA.guid = function() {
3492 return Math.floor((1 + Math.random()) * 0x10000)
3497 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3500 NETDATA.zeropad = function(x) {
3501 if(x > -10 && x < 10) return '0' + x.toString();
3502 else return x.toString();
3505 // user function to signal us the DOM has been
3507 NETDATA.updatedDom = function() {
3508 NETDATA.options.updated_dom = true;
3511 NETDATA.ready = function(callback) {
3512 NETDATA.options.pauseCallback = callback;
3515 NETDATA.pause = function(callback) {
3516 if(NETDATA.options.pause === true)
3519 NETDATA.options.pauseCallback = callback;
3522 NETDATA.unpause = function() {
3523 NETDATA.options.pauseCallback = null;
3524 NETDATA.options.updated_dom = true;
3525 NETDATA.options.pause = false;
3528 // ----------------------------------------------------------------------------------------------------------------
3530 // this is purely sequencial charts refresher
3531 // it is meant to be autonomous
3532 NETDATA.chartRefresherNoParallel = function(index) {
3533 if(NETDATA.options.debug.mail_loop === true)
3534 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3536 if(NETDATA.options.updated_dom === true) {
3537 // the dom has been updated
3538 // get the dom parts again
3539 NETDATA.parseDom(NETDATA.chartRefresher);
3542 if(index >= NETDATA.options.targets.length) {
3543 if(NETDATA.options.debug.main_loop === true)
3544 console.log('waiting to restart main loop...');
3546 NETDATA.options.auto_refresher_fast_weight = 0;
3548 setTimeout(function() {
3549 NETDATA.chartRefresher();
3550 }, NETDATA.options.current.idle_between_loops);
3553 var state = NETDATA.options.targets[index];
3555 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3556 if(NETDATA.options.debug.main_loop === true)
3557 console.log('fast rendering...');
3559 state.autoRefresh(function() {
3560 NETDATA.chartRefresherNoParallel(++index);
3564 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3565 NETDATA.options.auto_refresher_fast_weight = 0;
3567 setTimeout(function() {
3568 state.autoRefresh(function() {
3569 NETDATA.chartRefresherNoParallel(++index);
3571 }, NETDATA.options.current.idle_between_charts);
3576 // this is part of the parallel refresher
3577 // its cause is to refresh sequencially all the charts
3578 // that depend on chart library initialization
3579 // it will call the parallel refresher back
3580 // as soon as it sees a chart that its chart library
3582 NETDATA.chartRefresher_uninitialized = function() {
3583 if(NETDATA.options.updated_dom === true) {
3584 // the dom has been updated
3585 // get the dom parts again
3586 NETDATA.parseDom(NETDATA.chartRefresher);
3590 if(NETDATA.options.sequencial.length === 0)
3591 NETDATA.chartRefresher();
3593 var state = NETDATA.options.sequencial.pop();
3594 if(state.library.initialized === true)
3595 NETDATA.chartRefresher();
3597 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3601 NETDATA.chartRefresherWaitTime = function() {
3602 return NETDATA.options.current.idle_parallel_loops;
3605 // the default refresher
3606 // it will create 2 sets of charts:
3607 // - the ones that can be refreshed in parallel
3608 // - the ones that depend on something else
3609 // the first set will be executed in parallel
3610 // the second will be given to NETDATA.chartRefresher_uninitialized()
3611 NETDATA.chartRefresher = function() {
3612 // console.log('auto-refresher...');
3614 if(NETDATA.options.pause === true) {
3615 // console.log('auto-refresher is paused');
3616 setTimeout(NETDATA.chartRefresher,
3617 NETDATA.chartRefresherWaitTime());
3621 if(typeof NETDATA.options.pauseCallback === 'function') {
3622 // console.log('auto-refresher is calling pauseCallback');
3623 NETDATA.options.pause = true;
3624 NETDATA.options.pauseCallback();
3625 NETDATA.chartRefresher();
3629 if(NETDATA.options.current.parallel_refresher === false) {
3630 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3631 NETDATA.chartRefresherNoParallel(0);
3635 if(NETDATA.options.updated_dom === true) {
3636 // the dom has been updated
3637 // get the dom parts again
3638 // console.log('auto-refresher is calling parseDom()');
3639 NETDATA.parseDom(NETDATA.chartRefresher);
3643 var parallel = new Array();
3644 var targets = NETDATA.options.targets;
3645 var len = targets.length;
3648 state = targets[len];
3649 if(state.isVisible() === false || state.running === true)
3652 if(state.library.initialized === false) {
3653 if(state.library.enabled === true) {
3654 state.library.initialize(NETDATA.chartRefresher);
3658 state.error('chart library "' + state.library_name + '" is not enabled.');
3662 parallel.unshift(state);
3665 if(parallel.length > 0) {
3666 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3667 // this will execute the jobs in parallel
3668 $(parallel).each(function() {
3673 // console.log('auto-refresher nothing to do');
3676 // run the next refresh iteration
3677 setTimeout(NETDATA.chartRefresher,
3678 NETDATA.chartRefresherWaitTime());
3681 NETDATA.parseDom = function(callback) {
3682 NETDATA.options.last_page_scroll = Date.now();
3683 NETDATA.options.updated_dom = false;
3685 var targets = $('div[data-netdata]'); //.filter(':visible');
3687 if(NETDATA.options.debug.main_loop === true)
3688 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3690 NETDATA.options.targets = new Array();
3691 var len = targets.length;
3693 // the initialization will take care of sizing
3694 // and the "loading..." message
3695 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3698 if(typeof callback === 'function') callback();
3701 // this is the main function - where everything starts
3702 NETDATA.start = function() {
3703 // this should be called only once
3705 NETDATA.options.page_is_visible = true;
3707 $(window).blur(function() {
3708 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3709 NETDATA.options.page_is_visible = false;
3710 if(NETDATA.options.debug.focus === true)
3711 console.log('Lost Focus!');
3715 $(window).focus(function() {
3716 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3717 NETDATA.options.page_is_visible = true;
3718 if(NETDATA.options.debug.focus === true)
3719 console.log('Focus restored!');
3723 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3724 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3725 NETDATA.options.page_is_visible = false;
3726 if(NETDATA.options.debug.focus === true)
3727 console.log('Document has no focus!');
3731 // bootstrap tab switching
3732 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3734 // bootstrap modal switching
3735 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3736 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3738 // bootstrap collapse switching
3739 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3740 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3742 NETDATA.parseDom(NETDATA.chartRefresher);
3744 // Alarms initialization
3745 setTimeout(NETDATA.alarms.init, 1000);
3747 // Registry initialization
3748 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3750 if(typeof netdataCallback === 'function')
3754 // ----------------------------------------------------------------------------------------------------------------
3757 NETDATA.peityInitialize = function(callback) {
3758 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3760 url: NETDATA.peity_js,
3763 xhrFields: { withCredentials: true } // required for the cookie
3766 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3769 NETDATA.chartLibraries.peity.enabled = false;
3770 NETDATA.error(100, NETDATA.peity_js);
3772 .always(function() {
3773 if(typeof callback === "function")
3778 NETDATA.chartLibraries.peity.enabled = false;
3779 if(typeof callback === "function")
3784 NETDATA.peityChartUpdate = function(state, data) {
3785 state.peity_instance.innerHTML = data.result;
3787 if(state.peity_options.stroke !== state.chartColors()[0]) {
3788 state.peity_options.stroke = state.chartColors()[0];
3789 if(state.chart.chart_type === 'line')
3790 state.peity_options.fill = NETDATA.themes.current.background;
3792 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3795 $(state.peity_instance).peity('line', state.peity_options);
3799 NETDATA.peityChartCreate = function(state, data) {
3800 state.peity_instance = document.createElement('div');
3801 state.element_chart.appendChild(state.peity_instance);
3803 var self = $(state.element);
3804 state.peity_options = {
3805 stroke: NETDATA.themes.current.foreground,
3806 strokeWidth: self.data('peity-strokewidth') || 1,
3807 width: state.chartWidth(),
3808 height: state.chartHeight(),
3809 fill: NETDATA.themes.current.foreground
3812 NETDATA.peityChartUpdate(state, data);
3816 // ----------------------------------------------------------------------------------------------------------------
3819 NETDATA.sparklineInitialize = function(callback) {
3820 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3822 url: NETDATA.sparkline_js,
3825 xhrFields: { withCredentials: true } // required for the cookie
3828 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3831 NETDATA.chartLibraries.sparkline.enabled = false;
3832 NETDATA.error(100, NETDATA.sparkline_js);
3834 .always(function() {
3835 if(typeof callback === "function")
3840 NETDATA.chartLibraries.sparkline.enabled = false;
3841 if(typeof callback === "function")
3846 NETDATA.sparklineChartUpdate = function(state, data) {
3847 state.sparkline_options.width = state.chartWidth();
3848 state.sparkline_options.height = state.chartHeight();
3850 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3854 NETDATA.sparklineChartCreate = function(state, data) {
3855 var self = $(state.element);
3856 var type = self.data('sparkline-type') || 'line';
3857 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3858 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3859 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3860 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3861 var composite = self.data('sparkline-composite') || undefined;
3862 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3863 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3864 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3865 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3866 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3867 var spotColor = self.data('sparkline-spotcolor') || undefined;
3868 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3869 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3870 var spotRadius = self.data('sparkline-spotradius') || undefined;
3871 var valueSpots = self.data('sparkline-valuespots') || undefined;
3872 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3873 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3874 var lineWidth = self.data('sparkline-linewidth') || undefined;
3875 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3876 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3877 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3878 var xvalues = self.data('sparkline-xvalues') || undefined;
3879 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3880 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3881 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3882 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3883 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3884 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3885 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3886 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3887 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3888 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3889 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3890 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3891 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3892 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3893 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3894 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3895 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3896 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3897 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3898 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3899 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3900 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3902 if(spotColor === 'disable') spotColor='';
3903 if(minSpotColor === 'disable') minSpotColor='';
3904 if(maxSpotColor === 'disable') maxSpotColor='';
3906 state.sparkline_options = {
3908 lineColor: lineColor,
3909 fillColor: fillColor,
3910 chartRangeMin: chartRangeMin,
3911 chartRangeMax: chartRangeMax,
3912 composite: composite,
3913 enableTagOptions: enableTagOptions,
3914 tagOptionPrefix: tagOptionPrefix,
3915 tagValuesAttribute: tagValuesAttribute,
3916 disableHiddenCheck: disableHiddenCheck,
3917 defaultPixelsPerValue: defaultPixelsPerValue,
3918 spotColor: spotColor,
3919 minSpotColor: minSpotColor,
3920 maxSpotColor: maxSpotColor,
3921 spotRadius: spotRadius,
3922 valueSpots: valueSpots,
3923 highlightSpotColor: highlightSpotColor,
3924 highlightLineColor: highlightLineColor,
3925 lineWidth: lineWidth,
3926 normalRangeMin: normalRangeMin,
3927 normalRangeMax: normalRangeMax,
3928 drawNormalOnTop: drawNormalOnTop,
3930 chartRangeClip: chartRangeClip,
3931 chartRangeMinX: chartRangeMinX,
3932 chartRangeMaxX: chartRangeMaxX,
3933 disableInteraction: disableInteraction,
3934 disableTooltips: disableTooltips,
3935 disableHighlight: disableHighlight,
3936 highlightLighten: highlightLighten,
3937 highlightColor: highlightColor,
3938 tooltipContainer: tooltipContainer,
3939 tooltipClassname: tooltipClassname,
3940 tooltipChartTitle: state.title,
3941 tooltipFormat: tooltipFormat,
3942 tooltipPrefix: tooltipPrefix,
3943 tooltipSuffix: tooltipSuffix,
3944 tooltipSkipNull: tooltipSkipNull,
3945 tooltipValueLookups: tooltipValueLookups,
3946 tooltipFormatFieldlist: tooltipFormatFieldlist,
3947 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3948 numberFormatter: numberFormatter,
3949 numberDigitGroupSep: numberDigitGroupSep,
3950 numberDecimalMark: numberDecimalMark,
3951 numberDigitGroupCount: numberDigitGroupCount,
3952 animatedZooms: animatedZooms,
3953 width: state.chartWidth(),
3954 height: state.chartHeight()
3957 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3961 // ----------------------------------------------------------------------------------------------------------------
3968 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3969 if(after < state.netdata_first)
3970 after = state.netdata_first;
3972 if(before > state.netdata_last)
3973 before = state.netdata_last;
3975 state.setMode('zoom');
3976 state.globalSelectionSyncStop();
3977 state.globalSelectionSyncDelay();
3978 state.dygraph_user_action = true;
3979 state.dygraph_force_zoom = true;
3980 state.updateChartPanOrZoom(after, before);
3981 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3984 NETDATA.dygraphSetSelection = function(state, t) {
3985 if(typeof state.dygraph_instance !== 'undefined') {
3986 var r = state.calculateRowForTime(t);
3988 state.dygraph_instance.setSelection(r);
3990 state.dygraph_instance.clearSelection();
3991 state.legendShowUndefined();
3998 NETDATA.dygraphClearSelection = function(state, t) {
3999 if(typeof state.dygraph_instance !== 'undefined') {
4000 state.dygraph_instance.clearSelection();
4005 NETDATA.dygraphSmoothInitialize = function(callback) {
4007 url: NETDATA.dygraph_smooth_js,
4010 xhrFields: { withCredentials: true } // required for the cookie
4013 NETDATA.dygraph.smooth = true;
4014 smoothPlotter.smoothing = 0.3;
4017 NETDATA.dygraph.smooth = false;
4019 .always(function() {
4020 if(typeof callback === "function")
4025 NETDATA.dygraphInitialize = function(callback) {
4026 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4028 url: NETDATA.dygraph_js,
4031 xhrFields: { withCredentials: true } // required for the cookie
4034 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4037 NETDATA.chartLibraries.dygraph.enabled = false;
4038 NETDATA.error(100, NETDATA.dygraph_js);
4040 .always(function() {
4041 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4042 NETDATA.dygraphSmoothInitialize(callback);
4043 else if(typeof callback === "function")
4048 NETDATA.chartLibraries.dygraph.enabled = false;
4049 if(typeof callback === "function")
4054 NETDATA.dygraphChartUpdate = function(state, data) {
4055 var dygraph = state.dygraph_instance;
4057 if(typeof dygraph === 'undefined')
4058 return NETDATA.dygraphChartCreate(state, data);
4060 // when the chart is not visible, and hidden
4061 // if there is a window resize, dygraph detects
4062 // its element size as 0x0.
4063 // this will make it re-appear properly
4065 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4069 file: data.result.data,
4070 colors: state.chartColors(),
4071 labels: data.result.labels,
4072 labelsDivWidth: state.chartWidth() - 70,
4073 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4076 if(state.dygraph_force_zoom === true) {
4077 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4078 state.log('dygraphChartUpdate() forced zoom update');
4080 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4081 options.isZoomedIgnoreProgrammaticZoom = true;
4082 state.dygraph_force_zoom = false;
4084 else if(state.current.name !== 'auto') {
4085 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4086 state.log('dygraphChartUpdate() loose update');
4089 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4090 state.log('dygraphChartUpdate() strict update');
4092 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4093 options.isZoomedIgnoreProgrammaticZoom = true;
4096 options.valueRange = state.dygraph_options.valueRange;
4098 var oldMax = null, oldMin = null;
4099 if(state.__commonMin !== null) {
4100 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4101 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4103 if(state.__commonMax !== null) {
4104 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4105 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4108 if(state.dygraph_smooth_eligible === true) {
4109 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4110 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4111 NETDATA.dygraphChartCreate(state, data);
4116 dygraph.updateOptions(options);
4119 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4120 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4121 options.valueRange[0] = NETDATA.commonMin.get(state);
4124 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4125 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4126 options.valueRange[1] = NETDATA.commonMax.get(state);
4130 if(redraw === true) {
4131 // state.log('forcing redraw to adapt to common- min/max');
4132 dygraph.updateOptions(options);
4135 state.dygraph_last_rendered = Date.now();
4139 NETDATA.dygraphChartCreate = function(state, data) {
4140 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4141 state.log('dygraphChartCreate()');
4143 var self = $(state.element);
4145 var chart_type = state.chart.chart_type;
4146 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4147 chart_type = self.data('dygraph-type') || chart_type;
4149 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4150 smooth = self.data('dygraph-smooth') || smooth;
4152 if(NETDATA.dygraph.smooth === false)
4155 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4156 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4158 state.dygraph_options = {
4159 colors: self.data('dygraph-colors') || state.chartColors(),
4161 // leave a few pixels empty on the right of the chart
4162 rightGap: self.data('dygraph-rightgap') || 5,
4163 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4164 showRoller: self.data('dygraph-showroller') || false,
4166 title: self.data('dygraph-title') || state.title,
4167 titleHeight: self.data('dygraph-titleheight') || 19,
4169 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4170 labels: data.result.labels,
4171 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4172 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4173 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4174 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4175 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4178 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4179 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4181 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4182 xRangePad: self.data('dygraph-xrangepad') || 0,
4183 yRangePad: self.data('dygraph-yrangepad') || 1,
4185 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4187 ylabel: state.units,
4188 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4190 // the function to plot the chart
4193 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4194 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4195 strokePattern: self.data('dygraph-strokepattern') || undefined,
4197 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4198 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4199 drawPoints: self.data('dygraph-drawpoints') || false,
4201 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4202 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4204 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4205 pointSize: self.data('dygraph-pointsize') || 1,
4207 // enabling this makes the chart with little square lines
4208 stepPlot: self.data('dygraph-stepplot') || false,
4210 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4211 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4212 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4214 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4215 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4216 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4217 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4219 drawAxis: self.data('dygraph-drawaxis') || true,
4220 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4221 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4222 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4224 drawGrid: self.data('dygraph-drawgrid') || true,
4225 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4226 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4227 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4229 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4230 sigFigs: self.data('dygraph-sigfigs') || null,
4231 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4232 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4234 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4235 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4236 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4238 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4239 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4243 ticker: Dygraph.dateTicker,
4244 axisLabelFormatter: function (d, gran) {
4245 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4247 valueFormatter: function (ms) {
4248 //var d = new Date(ms);
4249 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4251 // no need to return anything here
4258 valueFormatter: function (x) {
4259 // we format legends with the state object
4260 // no need to do anything here
4261 // return (Math.round(x*100) / 100).toLocaleString();
4262 // return state.legendFormatValue(x);
4267 legendFormatter: function(data) {
4268 var elements = state.element_legend_childs;
4270 // if the hidden div is not there
4271 // we are not managing the legend
4272 if(elements.hidden === null) return;
4274 if (typeof data.x !== 'undefined') {
4275 state.legendSetDate(data.x);
4276 var i = data.series.length;
4278 var series = data.series[i];
4279 if(series.isVisible === true)
4280 state.legendSetLabelValue(series.label, series.y);
4286 drawCallback: function(dygraph, is_initial) {
4287 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4288 state.dygraph_user_action = false;
4290 var x_range = dygraph.xAxisRange();
4291 var after = Math.round(x_range[0]);
4292 var before = Math.round(x_range[1]);
4294 if(NETDATA.options.debug.dygraph === true)
4295 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4297 if(before <= state.netdata_last && after >= state.netdata_first)
4298 state.updateChartPanOrZoom(after, before);
4301 zoomCallback: function(minDate, maxDate, yRanges) {
4302 if(NETDATA.options.debug.dygraph === true)
4303 state.log('dygraphZoomCallback()');
4305 state.globalSelectionSyncStop();
4306 state.globalSelectionSyncDelay();
4307 state.setMode('zoom');
4309 // refresh it to the greatest possible zoom level
4310 state.dygraph_user_action = true;
4311 state.dygraph_force_zoom = true;
4312 state.updateChartPanOrZoom(minDate, maxDate);
4314 highlightCallback: function(event, x, points, row, seriesName) {
4315 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4316 state.log('dygraphHighlightCallback()');
4320 // there is a bug in dygraph when the chart is zoomed enough
4321 // the time it thinks is selected is wrong
4322 // here we calculate the time t based on the row number selected
4324 var t = state.data_after + row * state.data_update_every;
4325 // 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);
4327 state.globalSelectionSync(x);
4329 // fix legend zIndex using the internal structures of dygraph legend module
4330 // this works, but it is a hack!
4331 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4333 unhighlightCallback: function(event) {
4334 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4335 state.log('dygraphUnhighlightCallback()');
4337 state.unpauseChart();
4338 state.globalSelectionSyncStop();
4340 interactionModel : {
4341 mousedown: function(event, dygraph, context) {
4342 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4343 state.log('interactionModel.mousedown()');
4345 state.dygraph_user_action = true;
4346 state.globalSelectionSyncStop();
4348 if(NETDATA.options.debug.dygraph === true)
4349 state.log('dygraphMouseDown()');
4351 // Right-click should not initiate a zoom.
4352 if(event.button && event.button === 2) return;
4354 context.initializeMouseDown(event, dygraph, context);
4356 if(event.button && event.button === 1) {
4357 if (event.altKey || event.shiftKey) {
4358 state.setMode('pan');
4359 state.globalSelectionSyncDelay();
4360 Dygraph.startPan(event, dygraph, context);
4363 state.setMode('zoom');
4364 state.globalSelectionSyncDelay();
4365 Dygraph.startZoom(event, dygraph, context);
4369 if (event.altKey || event.shiftKey) {
4370 state.setMode('zoom');
4371 state.globalSelectionSyncDelay();
4372 Dygraph.startZoom(event, dygraph, context);
4375 state.setMode('pan');
4376 state.globalSelectionSyncDelay();
4377 Dygraph.startPan(event, dygraph, context);
4381 mousemove: function(event, dygraph, context) {
4382 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4383 state.log('interactionModel.mousemove()');
4385 if(context.isPanning) {
4386 state.dygraph_user_action = true;
4387 state.globalSelectionSyncStop();
4388 state.globalSelectionSyncDelay();
4389 state.setMode('pan');
4390 context.is2DPan = false;
4391 Dygraph.movePan(event, dygraph, context);
4393 else if(context.isZooming) {
4394 state.dygraph_user_action = true;
4395 state.globalSelectionSyncStop();
4396 state.globalSelectionSyncDelay();
4397 state.setMode('zoom');
4398 Dygraph.moveZoom(event, dygraph, context);
4401 mouseup: function(event, dygraph, context) {
4402 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4403 state.log('interactionModel.mouseup()');
4405 if (context.isPanning) {
4406 state.dygraph_user_action = true;
4407 state.globalSelectionSyncDelay();
4408 Dygraph.endPan(event, dygraph, context);
4410 else if (context.isZooming) {
4411 state.dygraph_user_action = true;
4412 state.globalSelectionSyncDelay();
4413 Dygraph.endZoom(event, dygraph, context);
4416 click: function(event, dygraph, context) {
4417 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4418 state.log('interactionModel.click()');
4420 event.preventDefault();
4422 dblclick: function(event, dygraph, context) {
4423 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4424 state.log('interactionModel.dblclick()');
4425 NETDATA.resetAllCharts(state);
4427 wheel: function(event, dygraph, context) {
4428 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4429 state.log('interactionModel.wheel()');
4431 // Take the offset of a mouse event on the dygraph canvas and
4432 // convert it to a pair of percentages from the bottom left.
4433 // (Not top left, bottom is where the lower value is.)
4434 function offsetToPercentage(g, offsetX, offsetY) {
4435 // This is calculating the pixel offset of the leftmost date.
4436 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4437 var yar0 = g.yAxisRange(0);
4439 // This is calculating the pixel of the higest value. (Top pixel)
4440 var yOffset = g.toDomCoords(null, yar0[1])[1];
4442 // x y w and h are relative to the corner of the drawing area,
4443 // so that the upper corner of the drawing area is (0, 0).
4444 var x = offsetX - xOffset;
4445 var y = offsetY - yOffset;
4447 // This is computing the rightmost pixel, effectively defining the
4449 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4451 // This is computing the lowest pixel, effectively defining the height.
4452 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4454 // Percentage from the left.
4455 var xPct = w === 0 ? 0 : (x / w);
4456 // Percentage from the top.
4457 var yPct = h === 0 ? 0 : (y / h);
4459 // The (1-) part below changes it from "% distance down from the top"
4460 // to "% distance up from the bottom".
4461 return [xPct, (1-yPct)];
4464 // Adjusts [x, y] toward each other by zoomInPercentage%
4465 // Split it so the left/bottom axis gets xBias/yBias of that change and
4466 // tight/top gets (1-xBias)/(1-yBias) of that change.
4468 // If a bias is missing it splits it down the middle.
4469 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4470 xBias = xBias || 0.5;
4471 yBias = yBias || 0.5;
4473 function adjustAxis(axis, zoomInPercentage, bias) {
4474 var delta = axis[1] - axis[0];
4475 var increment = delta * zoomInPercentage;
4476 var foo = [increment * bias, increment * (1-bias)];
4478 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4481 var yAxes = g.yAxisRanges();
4483 for (var i = 0; i < yAxes.length; i++) {
4484 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4487 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4490 if(event.altKey || event.shiftKey) {
4491 state.dygraph_user_action = true;
4493 state.globalSelectionSyncStop();
4494 state.globalSelectionSyncDelay();
4496 // http://dygraphs.com/gallery/interaction-api.js
4498 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4500 normal_def = event.wheelDelta / 40;
4503 normal_def = event.deltaY * -1.2;
4505 var normal = (event.detail) ? event.detail * -1 : normal_def;
4506 var percentage = normal / 50;
4508 if (!(event.offsetX && event.offsetY)){
4509 event.offsetX = event.layerX - event.target.offsetLeft;
4510 event.offsetY = event.layerY - event.target.offsetTop;
4513 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4514 var xPct = percentages[0];
4515 var yPct = percentages[1];
4517 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4518 var after = new_x_range[0];
4519 var before = new_x_range[1];
4521 var first = state.netdata_first + state.data_update_every;
4522 var last = state.netdata_last + state.data_update_every;
4525 after -= (before - last);
4532 state.setMode('zoom');
4533 if(state.updateChartPanOrZoom(after, before) === true)
4534 dygraph.updateOptions({ dateWindow: [ after, before ] });
4536 event.preventDefault();
4539 touchstart: function(event, dygraph, context) {
4540 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4541 state.log('interactionModel.touchstart()');
4543 state.dygraph_user_action = true;
4544 state.setMode('zoom');
4547 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4549 // we overwrite the touch directions at the end, to overwrite
4550 // the internal default of dygraphs
4551 context.touchDirections = { x: true, y: false };
4553 state.dygraph_last_touch_start = Date.now();
4554 state.dygraph_last_touch_move = 0;
4556 if(typeof event.touches[0].pageX === 'number')
4557 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4559 state.dygraph_last_touch_page_x = 0;
4561 touchmove: function(event, dygraph, context) {
4562 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4563 state.log('interactionModel.touchmove()');
4565 state.dygraph_user_action = true;
4566 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4568 state.dygraph_last_touch_move = Date.now();
4570 touchend: function(event, dygraph, context) {
4571 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4572 state.log('interactionModel.touchend()');
4574 state.dygraph_user_action = true;
4575 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4577 // if it didn't move, it is a selection
4578 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4579 // internal api of dygraphs
4580 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4581 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4582 if(NETDATA.dygraphSetSelection(state, t) === true)
4583 state.globalSelectionSync(t);
4586 // if it was double tap within double click time, reset the charts
4587 var now = Date.now();
4588 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4589 if(state.dygraph_last_touch_move === 0) {
4590 var dt = now - state.dygraph_last_touch_end;
4591 if(dt <= NETDATA.options.current.double_click_speed)
4592 NETDATA.resetAllCharts(state);
4596 // remember the timestamp of the last touch end
4597 state.dygraph_last_touch_end = now;
4602 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4603 state.dygraph_options.drawGrid = false;
4604 state.dygraph_options.drawAxis = false;
4605 state.dygraph_options.title = undefined;
4606 state.dygraph_options.ylabel = undefined;
4607 state.dygraph_options.yLabelWidth = 0;
4608 state.dygraph_options.labelsDivWidth = 120;
4609 state.dygraph_options.labelsDivStyles.width = '120px';
4610 state.dygraph_options.labelsSeparateLines = true;
4611 state.dygraph_options.rightGap = 0;
4612 state.dygraph_options.yRangePad = 1;
4615 if(smooth === true) {
4616 state.dygraph_smooth_eligible = true;
4618 if(NETDATA.options.current.smooth_plot === true)
4619 state.dygraph_options.plotter = smoothPlotter;
4621 else state.dygraph_smooth_eligible = false;
4623 state.dygraph_instance = new Dygraph(state.element_chart,
4624 data.result.data, state.dygraph_options);
4626 state.dygraph_force_zoom = false;
4627 state.dygraph_user_action = false;
4628 state.dygraph_last_rendered = Date.now();
4630 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4631 state.__commonMin = self.data('common-min') || null;
4632 state.__commonMax = self.data('common-max') || null;
4635 state.log('incompatible version of dygraphs detected');
4636 state.__commonMin = null;
4637 state.__commonMax = null;
4643 // ----------------------------------------------------------------------------------------------------------------
4646 NETDATA.morrisInitialize = function(callback) {
4647 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4649 // morris requires raphael
4650 if(!NETDATA.chartLibraries.raphael.initialized) {
4651 if(NETDATA.chartLibraries.raphael.enabled) {
4652 NETDATA.raphaelInitialize(function() {
4653 NETDATA.morrisInitialize(callback);
4657 NETDATA.chartLibraries.morris.enabled = false;
4658 if(typeof callback === "function")
4663 NETDATA._loadCSS(NETDATA.morris_css);
4666 url: NETDATA.morris_js,
4669 xhrFields: { withCredentials: true } // required for the cookie
4672 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4675 NETDATA.chartLibraries.morris.enabled = false;
4676 NETDATA.error(100, NETDATA.morris_js);
4678 .always(function() {
4679 if(typeof callback === "function")
4685 NETDATA.chartLibraries.morris.enabled = false;
4686 if(typeof callback === "function")
4691 NETDATA.morrisChartUpdate = function(state, data) {
4692 state.morris_instance.setData(data.result.data);
4696 NETDATA.morrisChartCreate = function(state, data) {
4698 state.morris_options = {
4699 element: state.element_chart.id,
4700 data: data.result.data,
4702 ykeys: data.dimension_names,
4703 labels: data.dimension_names,
4709 continuousLine: false,
4710 behaveLikeLine: false
4713 if(state.chart.chart_type === 'line')
4714 state.morris_instance = new Morris.Line(state.morris_options);
4716 else if(state.chart.chart_type === 'area') {
4717 state.morris_options.behaveLikeLine = true;
4718 state.morris_instance = new Morris.Area(state.morris_options);
4721 state.morris_instance = new Morris.Area(state.morris_options);
4726 // ----------------------------------------------------------------------------------------------------------------
4729 NETDATA.raphaelInitialize = function(callback) {
4730 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4732 url: NETDATA.raphael_js,
4735 xhrFields: { withCredentials: true } // required for the cookie
4738 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4741 NETDATA.chartLibraries.raphael.enabled = false;
4742 NETDATA.error(100, NETDATA.raphael_js);
4744 .always(function() {
4745 if(typeof callback === "function")
4750 NETDATA.chartLibraries.raphael.enabled = false;
4751 if(typeof callback === "function")
4756 NETDATA.raphaelChartUpdate = function(state, data) {
4757 $(state.element_chart).raphael(data.result, {
4758 width: state.chartWidth(),
4759 height: state.chartHeight()
4765 NETDATA.raphaelChartCreate = function(state, data) {
4766 $(state.element_chart).raphael(data.result, {
4767 width: state.chartWidth(),
4768 height: state.chartHeight()
4774 // ----------------------------------------------------------------------------------------------------------------
4777 NETDATA.c3Initialize = function(callback) {
4778 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4781 if(!NETDATA.chartLibraries.d3.initialized) {
4782 if(NETDATA.chartLibraries.d3.enabled) {
4783 NETDATA.d3Initialize(function() {
4784 NETDATA.c3Initialize(callback);
4788 NETDATA.chartLibraries.c3.enabled = false;
4789 if(typeof callback === "function")
4794 NETDATA._loadCSS(NETDATA.c3_css);
4800 xhrFields: { withCredentials: true } // required for the cookie
4803 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4806 NETDATA.chartLibraries.c3.enabled = false;
4807 NETDATA.error(100, NETDATA.c3_js);
4809 .always(function() {
4810 if(typeof callback === "function")
4816 NETDATA.chartLibraries.c3.enabled = false;
4817 if(typeof callback === "function")
4822 NETDATA.c3ChartUpdate = function(state, data) {
4823 state.c3_instance.destroy();
4824 return NETDATA.c3ChartCreate(state, data);
4826 //state.c3_instance.load({
4827 // rows: data.result,
4834 NETDATA.c3ChartCreate = function(state, data) {
4836 state.element_chart.id = 'c3-' + state.uuid;
4837 // console.log('id = ' + state.element_chart.id);
4839 state.c3_instance = c3.generate({
4840 bindto: '#' + state.element_chart.id,
4842 width: state.chartWidth(),
4843 height: state.chartHeight()
4846 pattern: state.chartColors()
4851 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4857 format: function(x) {
4858 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4885 // console.log(state.c3_instance);
4890 // ----------------------------------------------------------------------------------------------------------------
4893 NETDATA.d3Initialize = function(callback) {
4894 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4899 xhrFields: { withCredentials: true } // required for the cookie
4902 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4905 NETDATA.chartLibraries.d3.enabled = false;
4906 NETDATA.error(100, NETDATA.d3_js);
4908 .always(function() {
4909 if(typeof callback === "function")
4914 NETDATA.chartLibraries.d3.enabled = false;
4915 if(typeof callback === "function")
4920 NETDATA.d3ChartUpdate = function(state, data) {
4924 NETDATA.d3ChartCreate = function(state, data) {
4928 // ----------------------------------------------------------------------------------------------------------------
4931 NETDATA.googleInitialize = function(callback) {
4932 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4934 url: NETDATA.google_js,
4937 xhrFields: { withCredentials: true } // required for the cookie
4940 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4941 google.load('visualization', '1.1', {
4942 'packages': ['corechart', 'controls'],
4943 'callback': callback
4947 NETDATA.chartLibraries.google.enabled = false;
4948 NETDATA.error(100, NETDATA.google_js);
4949 if(typeof callback === "function")
4954 NETDATA.chartLibraries.google.enabled = false;
4955 if(typeof callback === "function")
4960 NETDATA.googleChartUpdate = function(state, data) {
4961 var datatable = new google.visualization.DataTable(data.result);
4962 state.google_instance.draw(datatable, state.google_options);
4966 NETDATA.googleChartCreate = function(state, data) {
4967 var datatable = new google.visualization.DataTable(data.result);
4969 state.google_options = {
4970 colors: state.chartColors(),
4972 // do not set width, height - the chart resizes itself
4973 //width: state.chartWidth(),
4974 //height: state.chartHeight(),
4979 // title: "Time of Day",
4980 // format:'HH:mm:ss',
4981 viewWindowMode: 'maximized',
4993 viewWindowMode: 'pretty',
5008 focusTarget: 'category',
5015 titlePosition: 'out',
5026 curveType: 'function',
5031 switch(state.chart.chart_type) {
5033 state.google_options.vAxis.viewWindowMode = 'maximized';
5034 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5035 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5039 state.google_options.isStacked = true;
5040 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5041 state.google_options.vAxis.viewWindowMode = 'maximized';
5042 state.google_options.vAxis.minValue = null;
5043 state.google_options.vAxis.maxValue = null;
5044 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5049 state.google_options.lineWidth = 2;
5050 state.google_instance = new google.visualization.LineChart(state.element_chart);
5054 state.google_instance.draw(datatable, state.google_options);
5058 // ----------------------------------------------------------------------------------------------------------------
5060 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5061 if(typeof value !== 'number') value = 0;
5062 if(typeof min !== 'number') min = 0;
5063 if(typeof max !== 'number') max = 0;
5065 if(min > value) min = value;
5066 if(max < value) max = value;
5068 // make sure it is zero based
5069 if(min > 0) min = 0;
5070 if(max < 0) max = 0;
5075 pcent = Math.round(value * 100 / max);
5076 if(pcent === 0) pcent = 0.1;
5080 pcent = Math.round(-value * 100 / min);
5081 if(pcent === 0) pcent = -0.1;
5087 // ----------------------------------------------------------------------------------------------------------------
5090 NETDATA.easypiechartInitialize = function(callback) {
5091 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5093 url: NETDATA.easypiechart_js,
5096 xhrFields: { withCredentials: true } // required for the cookie
5099 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5102 NETDATA.chartLibraries.easypiechart.enabled = false;
5103 NETDATA.error(100, NETDATA.easypiechart_js);
5105 .always(function() {
5106 if(typeof callback === "function")
5111 NETDATA.chartLibraries.easypiechart.enabled = false;
5112 if(typeof callback === "function")
5117 NETDATA.easypiechartClearSelection = function(state) {
5118 if(typeof state.easyPieChartEvent !== 'undefined') {
5119 if(state.easyPieChartEvent.timer !== null)
5120 clearTimeout(state.easyPieChartEvent.timer);
5122 state.easyPieChartEvent.timer = null;
5125 if(state.isAutoRefreshable() === true && state.data !== null) {
5126 NETDATA.easypiechartChartUpdate(state, state.data);
5129 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5130 state.easyPieChart_instance.update(0);
5132 state.easyPieChart_instance.enableAnimation();
5137 NETDATA.easypiechartSetSelection = function(state, t) {
5138 if(state.timeIsVisible(t) !== true)
5139 return NETDATA.easypiechartClearSelection(state);
5141 var slot = state.calculateRowForTime(t);
5142 if(slot < 0 || slot >= state.data.result.length)
5143 return NETDATA.easypiechartClearSelection(state);
5145 if(typeof state.easyPieChartEvent === 'undefined') {
5146 state.easyPieChartEvent = {
5153 var value = state.data.result[state.data.result.length - 1 - slot];
5154 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5155 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5156 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5158 state.easyPieChartEvent.value = value;
5159 state.easyPieChartEvent.pcent = pcent;
5160 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5162 if(state.easyPieChartEvent.timer === null) {
5163 state.easyPieChart_instance.disableAnimation();
5165 state.easyPieChartEvent.timer = setTimeout(function() {
5166 state.easyPieChartEvent.timer = null;
5167 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5168 }, NETDATA.options.current.charts_selection_animation_delay);
5174 NETDATA.easypiechartChartUpdate = function(state, data) {
5175 var value, min, max, pcent;
5177 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5182 value = data.result[0];
5183 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5184 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5185 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5188 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5189 state.easyPieChart_instance.update(pcent);
5193 NETDATA.easypiechartChartCreate = function(state, data) {
5194 var self = $(state.element);
5195 var chart = $(state.element_chart);
5197 var value = data.result[0];
5198 var min = self.data('easypiechart-min-value') || null;
5199 var max = self.data('easypiechart-max-value') || null;
5200 var adjust = self.data('easypiechart-adjust') || null;
5203 min = NETDATA.commonMin.get(state);
5204 state.easyPieChartMin = null;
5207 state.easyPieChartMin = min;
5210 max = NETDATA.commonMax.get(state);
5211 state.easyPieChartMax = null;
5214 state.easyPieChartMax = max;
5216 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5218 chart.data('data-percent', pcent);
5222 case 'width': size = state.chartHeight(); break;
5223 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5224 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5226 default: size = state.chartWidth(); break;
5228 state.element.style.width = size + 'px';
5229 state.element.style.height = size + 'px';
5231 var stroke = Math.floor(size / 22);
5232 if(stroke < 3) stroke = 2;
5234 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5235 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5236 state.easyPieChartLabel = document.createElement('span');
5237 state.easyPieChartLabel.className = 'easyPieChartLabel';
5238 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5239 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5240 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5241 state.element_chart.appendChild(state.easyPieChartLabel);
5243 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5244 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5245 state.easyPieChartTitle = document.createElement('span');
5246 state.easyPieChartTitle.className = 'easyPieChartTitle';
5247 state.easyPieChartTitle.innerText = state.title;
5248 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5249 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5250 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5251 state.element_chart.appendChild(state.easyPieChartTitle);
5253 var unitfontsize = Math.round(titlefontsize * 0.9);
5254 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5255 state.easyPieChartUnits = document.createElement('span');
5256 state.easyPieChartUnits.className = 'easyPieChartUnits';
5257 state.easyPieChartUnits.innerText = state.units;
5258 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5259 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5260 state.element_chart.appendChild(state.easyPieChartUnits);
5262 var barColor = self.data('easypiechart-barcolor');
5263 if(typeof barColor === 'undefined' || barColor === null)
5264 barColor = state.chartColors()[0];
5266 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5267 var tmp = eval(barColor);
5268 if(typeof tmp === 'function')
5272 chart.easyPieChart({
5274 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5275 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5276 scaleLength: self.data('easypiechart-scalelength') || 5,
5277 lineCap: self.data('easypiechart-linecap') || 'round',
5278 lineWidth: self.data('easypiechart-linewidth') || stroke,
5279 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5280 size: self.data('easypiechart-size') || size,
5281 rotate: self.data('easypiechart-rotate') || 0,
5282 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5283 easing: self.data('easypiechart-easing') || undefined
5286 // when we just re-create the chart
5287 // do not animate the first update
5289 if(typeof state.easyPieChart_instance !== 'undefined')
5292 state.easyPieChart_instance = chart.data('easyPieChart');
5293 if(animate === false) state.easyPieChart_instance.disableAnimation();
5294 state.easyPieChart_instance.update(pcent);
5295 if(animate === false) state.easyPieChart_instance.enableAnimation();
5299 // ----------------------------------------------------------------------------------------------------------------
5302 NETDATA.gaugeInitialize = function(callback) {
5303 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5305 url: NETDATA.gauge_js,
5308 xhrFields: { withCredentials: true } // required for the cookie
5311 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5314 NETDATA.chartLibraries.gauge.enabled = false;
5315 NETDATA.error(100, NETDATA.gauge_js);
5317 .always(function() {
5318 if(typeof callback === "function")
5323 NETDATA.chartLibraries.gauge.enabled = false;
5324 if(typeof callback === "function")
5329 NETDATA.gaugeAnimation = function(state, status) {
5332 if(typeof status === 'boolean' && status === false)
5334 else if(typeof status === 'number')
5337 // console.log('gauge speed ' + speed);
5338 state.gauge_instance.animationSpeed = speed;
5339 state.___gaugeOld__.speed = speed;
5342 NETDATA.gaugeSet = function(state, value, min, max) {
5343 if(typeof value !== 'number') value = 0;
5344 if(typeof min !== 'number') min = 0;
5345 if(typeof max !== 'number') max = 0;
5346 if(value > max) max = value;
5347 if(value < min) min = value;
5356 // gauge.js has an issue if the needle
5357 // is smaller than min or larger than max
5358 // when we set the new values
5359 // the needle will go crazy
5361 // to prevent it, we always feed it
5362 // with a percentage, so that the needle
5363 // is always between min and max
5364 var pcent = (value - min) * 100 / (max - min);
5366 // these should never happen
5367 if(pcent < 0) pcent = 0;
5368 if(pcent > 100) pcent = 100;
5370 state.gauge_instance.set(pcent);
5371 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5373 state.___gaugeOld__.value = value;
5374 state.___gaugeOld__.min = min;
5375 state.___gaugeOld__.max = max;
5378 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5379 if(state.___gaugeOld__.valueLabel !== value) {
5380 state.___gaugeOld__.valueLabel = value;
5381 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5383 if(state.___gaugeOld__.minLabel !== min) {
5384 state.___gaugeOld__.minLabel = min;
5385 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5387 if(state.___gaugeOld__.maxLabel !== max) {
5388 state.___gaugeOld__.maxLabel = max;
5389 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5393 NETDATA.gaugeClearSelection = function(state) {
5394 if(typeof state.gaugeEvent !== 'undefined') {
5395 if(state.gaugeEvent.timer !== null)
5396 clearTimeout(state.gaugeEvent.timer);
5398 state.gaugeEvent.timer = null;
5401 if(state.isAutoRefreshable() === true && state.data !== null) {
5402 NETDATA.gaugeChartUpdate(state, state.data);
5405 NETDATA.gaugeAnimation(state, false);
5406 NETDATA.gaugeSet(state, null, null, null);
5407 NETDATA.gaugeSetLabels(state, null, null, null);
5410 NETDATA.gaugeAnimation(state, true);
5414 NETDATA.gaugeSetSelection = function(state, t) {
5415 if(state.timeIsVisible(t) !== true)
5416 return NETDATA.gaugeClearSelection(state);
5418 var slot = state.calculateRowForTime(t);
5419 if(slot < 0 || slot >= state.data.result.length)
5420 return NETDATA.gaugeClearSelection(state);
5422 if(typeof state.gaugeEvent === 'undefined') {
5423 state.gaugeEvent = {
5431 var value = state.data.result[state.data.result.length - 1 - slot];
5432 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5433 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5435 // make sure it is zero based
5436 if(min > 0) min = 0;
5437 if(max < 0) max = 0;
5439 state.gaugeEvent.value = value;
5440 state.gaugeEvent.min = min;
5441 state.gaugeEvent.max = max;
5442 NETDATA.gaugeSetLabels(state, value, min, max);
5444 if(state.gaugeEvent.timer === null) {
5445 NETDATA.gaugeAnimation(state, false);
5447 state.gaugeEvent.timer = setTimeout(function() {
5448 state.gaugeEvent.timer = null;
5449 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5450 }, NETDATA.options.current.charts_selection_animation_delay);
5456 NETDATA.gaugeChartUpdate = function(state, data) {
5457 var value, min, max;
5459 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5463 NETDATA.gaugeSetLabels(state, null, null, null);
5466 value = data.result[0];
5467 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5468 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5469 if(value < min) min = value;
5470 if(value > max) max = value;
5472 // make sure it is zero based
5473 if(min > 0) min = 0;
5474 if(max < 0) max = 0;
5476 NETDATA.gaugeSetLabels(state, value, min, max);
5479 NETDATA.gaugeSet(state, value, min, max);
5483 NETDATA.gaugeChartCreate = function(state, data) {
5484 var self = $(state.element);
5485 // var chart = $(state.element_chart);
5487 var value = data.result[0];
5488 var min = self.data('gauge-min-value') || null;
5489 var max = self.data('gauge-max-value') || null;
5490 var adjust = self.data('gauge-adjust') || null;
5491 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5492 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5493 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5494 var stopColor = self.data('gauge-stop-color') || void 0;
5495 var generateGradient = self.data('gauge-generate-gradient') || false;
5498 min = NETDATA.commonMin.get(state);
5499 state.gaugeMin = null;
5502 state.gaugeMin = min;
5505 max = NETDATA.commonMax.get(state);
5506 state.gaugeMax = null;
5509 state.gaugeMax = max;
5511 // make sure it is zero based
5512 if(min > 0) min = 0;
5513 if(max < 0) max = 0;
5515 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5517 // case 'width': width = height * ratio; break;
5519 // default: height = width / ratio; break;
5521 //state.element.style.width = width.toString() + 'px';
5522 //state.element.style.height = height.toString() + 'px';
5527 lines: 12, // The number of lines to draw
5528 angle: 0.15, // The length of each line
5529 lineWidth: 0.44, // 0.44 The line thickness
5531 length: 0.8, // 0.9 The radius of the inner circle
5532 strokeWidth: 0.035, // The rotation offset
5533 color: pointerColor // Fill color
5535 colorStart: startColor, // Colors
5536 colorStop: stopColor, // just experiment with them
5537 strokeColor: strokeColor, // to see which ones work best for you
5539 generateGradient: (generateGradient === true)?true:false,
5543 if (generateGradient.constructor === Array) {
5545 // data-gauge-generate-gradient="[0, 50, 100]"
5546 // data-gauge-gradient-percent-color-0="#FFFFFF"
5547 // data-gauge-gradient-percent-color-50="#999900"
5548 // data-gauge-gradient-percent-color-100="#000000"
5550 options.percentColors = new Array();
5551 var len = generateGradient.length;
5553 var pcent = generateGradient[len];
5554 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5555 if(color !== false) {
5556 var a = new Array();
5559 options.percentColors.unshift(a);
5562 if(options.percentColors.length === 0)
5563 delete options.percentColors;
5565 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5566 options.percentColors = [
5567 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5568 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5569 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5570 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5571 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5572 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5573 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5574 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5575 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5576 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5577 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5580 state.gauge_canvas = document.createElement('canvas');
5581 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5582 state.gauge_canvas.className = 'gaugeChart';
5583 state.gauge_canvas.width = width;
5584 state.gauge_canvas.height = height;
5585 state.element_chart.appendChild(state.gauge_canvas);
5587 var valuefontsize = Math.floor(height / 6);
5588 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5589 state.gaugeChartLabel = document.createElement('span');
5590 state.gaugeChartLabel.className = 'gaugeChartLabel';
5591 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5592 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5593 state.element_chart.appendChild(state.gaugeChartLabel);
5595 var titlefontsize = Math.round(valuefontsize / 2);
5597 state.gaugeChartTitle = document.createElement('span');
5598 state.gaugeChartTitle.className = 'gaugeChartTitle';
5599 state.gaugeChartTitle.innerText = state.title;
5600 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5601 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5602 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5603 state.element_chart.appendChild(state.gaugeChartTitle);
5605 var unitfontsize = Math.round(titlefontsize * 0.9);
5606 state.gaugeChartUnits = document.createElement('span');
5607 state.gaugeChartUnits.className = 'gaugeChartUnits';
5608 state.gaugeChartUnits.innerText = state.units;
5609 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5610 state.element_chart.appendChild(state.gaugeChartUnits);
5612 state.gaugeChartMin = document.createElement('span');
5613 state.gaugeChartMin.className = 'gaugeChartMin';
5614 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5615 state.element_chart.appendChild(state.gaugeChartMin);
5617 state.gaugeChartMax = document.createElement('span');
5618 state.gaugeChartMax.className = 'gaugeChartMax';
5619 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5620 state.element_chart.appendChild(state.gaugeChartMax);
5622 // when we just re-create the chart
5623 // do not animate the first update
5625 if(typeof state.gauge_instance !== 'undefined')
5628 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5630 state.___gaugeOld__ = {
5639 // we will always feed a percentage
5640 state.gauge_instance.minValue = 0;
5641 state.gauge_instance.maxValue = 100;
5643 NETDATA.gaugeAnimation(state, animate);
5644 NETDATA.gaugeSet(state, value, min, max);
5645 NETDATA.gaugeSetLabels(state, value, min, max);
5646 NETDATA.gaugeAnimation(state, true);
5650 // ----------------------------------------------------------------------------------------------------------------
5651 // Charts Libraries Registration
5653 NETDATA.chartLibraries = {
5655 initialize: NETDATA.dygraphInitialize,
5656 create: NETDATA.dygraphChartCreate,
5657 update: NETDATA.dygraphChartUpdate,
5658 resize: function(state) {
5659 if(typeof state.dygraph_instance.resize === 'function')
5660 state.dygraph_instance.resize();
5662 setSelection: NETDATA.dygraphSetSelection,
5663 clearSelection: NETDATA.dygraphClearSelection,
5664 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5667 format: function(state) { return 'json'; },
5668 options: function(state) { return 'ms|flip'; },
5669 legend: function(state) {
5670 if(this.isSparkline(state) === false)
5671 return 'right-side';
5675 autoresize: function(state) { return true; },
5676 max_updates_to_recreate: function(state) { return 5000; },
5677 track_colors: function(state) { return true; },
5678 pixels_per_point: function(state) {
5679 if(this.isSparkline(state) === false)
5685 isSparkline: function(state) {
5686 if(typeof state.dygraph_sparkline === 'undefined') {
5687 var t = $(state.element).data('dygraph-theme');
5688 if(t === 'sparkline')
5689 state.dygraph_sparkline = true;
5691 state.dygraph_sparkline = false;
5693 return state.dygraph_sparkline;
5697 initialize: NETDATA.sparklineInitialize,
5698 create: NETDATA.sparklineChartCreate,
5699 update: NETDATA.sparklineChartUpdate,
5701 setSelection: undefined, // function(state, t) { return true; },
5702 clearSelection: undefined, // function(state) { return true; },
5703 toolboxPanAndZoom: null,
5706 format: function(state) { return 'array'; },
5707 options: function(state) { return 'flip|abs'; },
5708 legend: function(state) { return null; },
5709 autoresize: function(state) { return false; },
5710 max_updates_to_recreate: function(state) { return 5000; },
5711 track_colors: function(state) { return false; },
5712 pixels_per_point: function(state) { return 3; }
5715 initialize: NETDATA.peityInitialize,
5716 create: NETDATA.peityChartCreate,
5717 update: NETDATA.peityChartUpdate,
5719 setSelection: undefined, // function(state, t) { return true; },
5720 clearSelection: undefined, // function(state) { return true; },
5721 toolboxPanAndZoom: null,
5724 format: function(state) { return 'ssvcomma'; },
5725 options: function(state) { return 'null2zero|flip|abs'; },
5726 legend: function(state) { return null; },
5727 autoresize: function(state) { return false; },
5728 max_updates_to_recreate: function(state) { return 5000; },
5729 track_colors: function(state) { return false; },
5730 pixels_per_point: function(state) { return 3; }
5733 initialize: NETDATA.morrisInitialize,
5734 create: NETDATA.morrisChartCreate,
5735 update: NETDATA.morrisChartUpdate,
5737 setSelection: undefined, // function(state, t) { return true; },
5738 clearSelection: undefined, // function(state) { return true; },
5739 toolboxPanAndZoom: null,
5742 format: function(state) { return 'json'; },
5743 options: function(state) { return 'objectrows|ms'; },
5744 legend: function(state) { return null; },
5745 autoresize: function(state) { return false; },
5746 max_updates_to_recreate: function(state) { return 50; },
5747 track_colors: function(state) { return false; },
5748 pixels_per_point: function(state) { return 15; }
5751 initialize: NETDATA.googleInitialize,
5752 create: NETDATA.googleChartCreate,
5753 update: NETDATA.googleChartUpdate,
5755 setSelection: undefined, //function(state, t) { return true; },
5756 clearSelection: undefined, //function(state) { return true; },
5757 toolboxPanAndZoom: null,
5760 format: function(state) { return 'datatable'; },
5761 options: function(state) { return ''; },
5762 legend: function(state) { return null; },
5763 autoresize: function(state) { return false; },
5764 max_updates_to_recreate: function(state) { return 300; },
5765 track_colors: function(state) { return false; },
5766 pixels_per_point: function(state) { return 4; }
5769 initialize: NETDATA.raphaelInitialize,
5770 create: NETDATA.raphaelChartCreate,
5771 update: NETDATA.raphaelChartUpdate,
5773 setSelection: undefined, // function(state, t) { return true; },
5774 clearSelection: undefined, // function(state) { return true; },
5775 toolboxPanAndZoom: null,
5778 format: function(state) { return 'json'; },
5779 options: function(state) { return ''; },
5780 legend: function(state) { return null; },
5781 autoresize: function(state) { return false; },
5782 max_updates_to_recreate: function(state) { return 5000; },
5783 track_colors: function(state) { return false; },
5784 pixels_per_point: function(state) { return 3; }
5787 initialize: NETDATA.c3Initialize,
5788 create: NETDATA.c3ChartCreate,
5789 update: NETDATA.c3ChartUpdate,
5791 setSelection: undefined, // function(state, t) { return true; },
5792 clearSelection: undefined, // function(state) { return true; },
5793 toolboxPanAndZoom: null,
5796 format: function(state) { return 'csvjsonarray'; },
5797 options: function(state) { return 'milliseconds'; },
5798 legend: function(state) { return null; },
5799 autoresize: function(state) { return false; },
5800 max_updates_to_recreate: function(state) { return 5000; },
5801 track_colors: function(state) { return false; },
5802 pixels_per_point: function(state) { return 15; }
5805 initialize: NETDATA.d3Initialize,
5806 create: NETDATA.d3ChartCreate,
5807 update: NETDATA.d3ChartUpdate,
5809 setSelection: undefined, // function(state, t) { return true; },
5810 clearSelection: undefined, // function(state) { return true; },
5811 toolboxPanAndZoom: null,
5814 format: function(state) { return 'json'; },
5815 options: function(state) { return ''; },
5816 legend: function(state) { return null; },
5817 autoresize: function(state) { return false; },
5818 max_updates_to_recreate: function(state) { return 5000; },
5819 track_colors: function(state) { return false; },
5820 pixels_per_point: function(state) { return 3; }
5823 initialize: NETDATA.easypiechartInitialize,
5824 create: NETDATA.easypiechartChartCreate,
5825 update: NETDATA.easypiechartChartUpdate,
5827 setSelection: NETDATA.easypiechartSetSelection,
5828 clearSelection: NETDATA.easypiechartClearSelection,
5829 toolboxPanAndZoom: null,
5832 format: function(state) { return 'array'; },
5833 options: function(state) { return 'absolute'; },
5834 legend: function(state) { return null; },
5835 autoresize: function(state) { return false; },
5836 max_updates_to_recreate: function(state) { return 5000; },
5837 track_colors: function(state) { return true; },
5838 pixels_per_point: function(state) { return 3; },
5842 initialize: NETDATA.gaugeInitialize,
5843 create: NETDATA.gaugeChartCreate,
5844 update: NETDATA.gaugeChartUpdate,
5846 setSelection: NETDATA.gaugeSetSelection,
5847 clearSelection: NETDATA.gaugeClearSelection,
5848 toolboxPanAndZoom: null,
5851 format: function(state) { return 'array'; },
5852 options: function(state) { return 'absolute'; },
5853 legend: function(state) { return null; },
5854 autoresize: function(state) { return false; },
5855 max_updates_to_recreate: function(state) { return 5000; },
5856 track_colors: function(state) { return true; },
5857 pixels_per_point: function(state) { return 3; },
5862 NETDATA.registerChartLibrary = function(library, url) {
5863 if(NETDATA.options.debug.libraries === true)
5864 console.log("registering chart library: " + library);
5866 NETDATA.chartLibraries[library].url = url;
5867 NETDATA.chartLibraries[library].initialized = true;
5868 NETDATA.chartLibraries[library].enabled = true;
5871 // ----------------------------------------------------------------------------------------------------------------
5872 // Load required JS libraries and CSS
5874 NETDATA.requiredJs = [
5876 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5878 isAlreadyLoaded: function() {
5879 // check if bootstrap is loaded
5880 if(typeof $().emulateTransitionEnd == 'function')
5883 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5891 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5892 isAlreadyLoaded: function() { return false; }
5896 NETDATA.requiredCSS = [
5898 url: NETDATA.themes.current.bootstrap_css,
5899 isAlreadyLoaded: function() {
5900 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5907 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5908 isAlreadyLoaded: function() { return false; }
5911 url: NETDATA.themes.current.dashboard_css,
5912 isAlreadyLoaded: function() { return false; }
5916 NETDATA.loadedRequiredJs = 0;
5917 NETDATA.loadRequiredJs = function(index, callback) {
5918 if(index >= NETDATA.requiredJs.length) {
5919 if(typeof callback === 'function')
5924 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5925 NETDATA.loadedRequiredJs++;
5926 NETDATA.loadRequiredJs(++index, callback);
5930 if(NETDATA.options.debug.main_loop === true)
5931 console.log('loading ' + NETDATA.requiredJs[index].url);
5934 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5938 url: NETDATA.requiredJs[index].url,
5941 xhrFields: { withCredentials: true } // required for the cookie
5944 if(NETDATA.options.debug.main_loop === true)
5945 console.log('loaded ' + NETDATA.requiredJs[index].url);
5948 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5950 .always(function() {
5951 NETDATA.loadedRequiredJs++;
5954 NETDATA.loadRequiredJs(++index, callback);
5958 NETDATA.loadRequiredJs(++index, callback);
5961 NETDATA.loadRequiredCSS = function(index) {
5962 if(index >= NETDATA.requiredCSS.length)
5965 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5966 NETDATA.loadRequiredCSS(++index);
5970 if(NETDATA.options.debug.main_loop === true)
5971 console.log('loading ' + NETDATA.requiredCSS[index].url);
5973 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5974 NETDATA.loadRequiredCSS(++index);
5978 // ----------------------------------------------------------------------------------------------------------------
5979 // Registry of netdata hosts
5982 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5983 chart_div_offset: 100, // give that space above the chart when scrolling to it
5984 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5985 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5987 ms_penalty: 0, // the time penalty of the next alarm
5988 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5989 // if alarms are shown faster than: one per 500ms
5991 notifications: false, // when true, the browser supports notifications (may not be granted though)
5992 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5993 first_notification_id: 0, // the id of the first alarm_log entry for this session
5994 // this is used to prevent CLEAR notifications for past events
5995 // notifications_shown: new Array(),
5997 server: null, // the server to connect to for fetching alarms
5998 current: null, // the list of raised alarms - updated in the background
5999 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6001 notify: function(entry) {
6002 // console.log('alarm ' + entry.unique_id);
6004 if(entry.updated === true) {
6005 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6009 var value = entry.value;
6010 if(NETDATA.alarms.current !== null) {
6011 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6012 if(typeof t !== 'undefined' && entry.status == t.status)
6016 var name = entry.name.replace(/_/g, ' ');
6017 var status = entry.status.toLowerCase();
6018 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6019 var tag = entry.alarm_id;
6020 var icon = 'images/seo-performance-128.png';
6021 var interaction = false;
6025 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6027 switch(entry.status) {
6035 case 'UNINITIALIZED':
6039 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6040 // console.log('alarm ' + entry.unique_id + ' is not current');
6043 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6044 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6047 title = name + ' back to normal';
6048 icon = 'images/check-mark-2-128-green.png'
6049 interaction = false;
6053 if(entry.old_status === 'CRITICAL')
6054 status = 'demoted to ' + entry.status.toLowerCase();
6056 icon = 'images/alert-128-orange.png';
6057 interaction = false;
6061 if(entry.old_status === 'WARNING')
6062 status = 'escalated to ' + entry.status.toLowerCase();
6064 icon = 'images/alert-128-red.png'
6069 console.log('invalid alarm status ' + entry.status);
6074 // cleanup old notifications with the same alarm_id as this one
6075 // FIXME: it does not seem to work on any web browser!
6076 var len = NETDATA.alarms.notifications_shown.length;
6078 var n = NETDATA.alarms.notifications_shown[len];
6079 if(n.data.alarm_id === entry.alarm_id) {
6080 console.log('removing old alarm ' + n.data.unique_id);
6082 // close the notification
6085 // remove it from the array
6086 NETDATA.alarms.notifications_shown.splice(len, 1);
6087 len = NETDATA.alarms.notifications_shown.length;
6094 setTimeout(function() {
6095 // show this notification
6096 // console.log('new notification: ' + title);
6097 var n = new Notification(title, {
6098 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6100 requireInteraction: interaction,
6101 icon: NETDATA.serverDefault + icon,
6105 n.onclick = function(event) {
6106 event.preventDefault();
6107 NETDATA.alarms.onclick(event.target.data);
6111 // NETDATA.alarms.notifications_shown.push(n);
6112 // console.log(entry);
6113 }, NETDATA.alarms.ms_penalty);
6115 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6119 scrollToChart: function(chart_id) {
6120 if(typeof chart_id === 'string') {
6121 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6122 if(typeof offset !== 'undefined') {
6123 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6130 scrollToAlarm: function(alarm) {
6131 if(typeof alarm === 'object') {
6132 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6134 if(ret === true && NETDATA.options.page_is_visible === false)
6136 // 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.');
6141 notifyAll: function() {
6142 // console.log('FETCHING ALARM LOG');
6143 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6144 // console.log('ALARM LOG FETCHED');
6146 if(data === null || typeof data !== 'object') {
6147 console.log('invalid alarms log response');
6151 if(data.length === 0) {
6152 console.log('received empty alarm log');
6156 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6158 data.sort(function(a, b) {
6159 if(a.unique_id > b.unique_id) return -1;
6160 if(a.unique_id < b.unique_id) return 1;
6164 NETDATA.alarms.ms_penalty = 0;
6166 var len = data.length;
6168 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6169 NETDATA.alarms.notify(data[len]);
6172 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6175 NETDATA.alarms.last_notification_id = data[0].unique_id;
6176 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6177 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6181 check_notifications: function() {
6182 // returns true if we should fire 1+ notifications
6184 if(NETDATA.alarms.notifications !== true) {
6185 // console.log('notifications not available');
6189 if(Notification.permission !== 'granted') {
6190 // console.log('notifications not granted');
6194 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6195 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6197 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6198 // console.log('new alarms detected');
6201 //else console.log('no new alarms');
6203 // else console.log('cannot process alarms');
6208 get: function(what, callback) {
6210 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6214 'Cache-Control': 'no-cache, no-store',
6215 'Pragma': 'no-cache'
6217 xhrFields: { withCredentials: true } // required for the cookie
6219 .done(function(data) {
6220 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6221 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6223 if(typeof callback === 'function')
6227 NETDATA.error(415, NETDATA.alarms.server);
6229 if(typeof callback === 'function')
6234 update_forever: function() {
6235 NETDATA.alarms.get('active', function(data) {
6237 NETDATA.alarms.current = data;
6239 if(NETDATA.alarms.check_notifications() === true) {
6240 NETDATA.alarms.notifyAll();
6243 if (typeof NETDATA.alarms.callback === 'function') {
6244 NETDATA.alarms.callback(data);
6247 // Health monitoring is disabled on this netdata
6248 if(data.status === false) return;
6251 setTimeout(NETDATA.alarms.update_forever, 10000);
6255 get_log: function(last_id, callback) {
6256 // console.log('fetching all log after ' + last_id.toString());
6258 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6262 'Cache-Control': 'no-cache, no-store',
6263 'Pragma': 'no-cache'
6265 xhrFields: { withCredentials: true } // required for the cookie
6267 .done(function(data) {
6268 if(typeof callback === 'function')
6272 NETDATA.error(416, NETDATA.alarms.server);
6274 if(typeof callback === 'function')
6280 var host = NETDATA.serverDefault;
6281 while(host.slice(-1) === '/')
6282 host = host.substring(0, host.length - 1);
6283 NETDATA.alarms.server = host;
6285 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6287 if(NETDATA.alarms.onclick === null)
6288 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6290 if(netdataShowAlarms === true) {
6291 NETDATA.alarms.update_forever();
6293 if('Notification' in window) {
6294 // console.log('notifications available');
6295 NETDATA.alarms.notifications = true;
6297 if(Notification.permission === 'default')
6298 Notification.requestPermission();
6304 // ----------------------------------------------------------------------------------------------------------------
6305 // Registry of netdata hosts
6307 NETDATA.registry = {
6308 server: null, // the netdata registry server
6309 person_guid: null, // the unique ID of this browser / user
6310 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6311 hostname: null, // the hostname of the netdata server that served dashboard.js
6312 machines: null, // the user's other URLs
6313 machines_array: null, // the user's other URLs in an array
6316 parsePersonUrls: function(person_urls) {
6317 // console.log(person_urls);
6318 NETDATA.registry.person_urls = person_urls;
6321 NETDATA.registry.machines = {};
6322 NETDATA.registry.machines_array = new Array();
6324 var now = Date.now();
6325 var apu = person_urls;
6328 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6329 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6335 accesses: apu[i][3],
6337 alternate_urls: new Array()
6339 obj.alternate_urls.push(apu[i][1]);
6341 NETDATA.registry.machines[apu[i][0]] = obj;
6342 NETDATA.registry.machines_array.push(obj);
6345 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6347 var pu = NETDATA.registry.machines[apu[i][0]];
6348 if(pu.last_t < apu[i][2]) {
6350 pu.last_t = apu[i][2];
6351 pu.name = apu[i][4];
6353 pu.accesses += apu[i][3];
6354 pu.alternate_urls.push(apu[i][1]);
6359 if(typeof netdataRegistryCallback === 'function')
6360 netdataRegistryCallback(NETDATA.registry.machines_array);
6364 if(netdataRegistry !== true) return;
6366 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6368 NETDATA.registry.server = data.registry;
6369 NETDATA.registry.machine_guid = data.machine_guid;
6370 NETDATA.registry.hostname = data.hostname;
6372 NETDATA.registry.access(2, function (person_urls) {
6373 NETDATA.registry.parsePersonUrls(person_urls);
6380 hello: function(host, callback) {
6381 while(host.slice(-1) === '/')
6382 host = host.substring(0, host.length - 1);
6384 // send HELLO to a netdata server:
6385 // 1. verifies the server is reachable
6386 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6388 url: host + '/api/v1/registry?action=hello',
6392 'Cache-Control': 'no-cache, no-store',
6393 'Pragma': 'no-cache'
6395 xhrFields: { withCredentials: true } // required for the cookie
6397 .done(function(data) {
6398 if(typeof data.status !== 'string' || data.status !== 'ok') {
6399 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6403 if(typeof callback === 'function')
6407 NETDATA.error(407, host);
6409 if(typeof callback === 'function')
6414 access: function(max_redirects, callback) {
6415 // send ACCESS to a netdata registry:
6416 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6417 // 2. it responds with a list of netdata servers we know
6418 // the registry identifies us using a cookie it sets the first time we access it
6419 // the registry may respond with a redirect URL to send us to another registry
6421 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),
6425 'Cache-Control': 'no-cache, no-store',
6426 'Pragma': 'no-cache'
6428 xhrFields: { withCredentials: true } // required for the cookie
6430 .done(function(data) {
6431 var redirect = null;
6432 if(typeof data.registry === 'string')
6433 redirect = data.registry;
6435 if(typeof data.status !== 'string' || data.status !== 'ok') {
6436 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6441 if(redirect !== null && max_redirects > 0) {
6442 NETDATA.registry.server = redirect;
6443 NETDATA.registry.access(max_redirects - 1, callback);
6446 if(typeof callback === 'function')
6451 if(typeof data.person_guid === 'string')
6452 NETDATA.registry.person_guid = data.person_guid;
6454 if(typeof callback === 'function')
6455 callback(data.urls);
6459 NETDATA.error(410, NETDATA.registry.server);
6461 if(typeof callback === 'function')
6466 delete: function(delete_url, callback) {
6467 // send DELETE to a netdata registry:
6469 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),
6473 'Cache-Control': 'no-cache, no-store',
6474 'Pragma': 'no-cache'
6476 xhrFields: { withCredentials: true } // required for the cookie
6478 .done(function(data) {
6479 if(typeof data.status !== 'string' || data.status !== 'ok') {
6480 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6484 if(typeof callback === 'function')
6488 NETDATA.error(412, NETDATA.registry.server);
6490 if(typeof callback === 'function')
6495 search: function(machine_guid, callback) {
6496 // SEARCH for the URLs of a machine:
6498 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,
6502 'Cache-Control': 'no-cache, no-store',
6503 'Pragma': 'no-cache'
6505 xhrFields: { withCredentials: true } // required for the cookie
6507 .done(function(data) {
6508 if(typeof data.status !== 'string' || data.status !== 'ok') {
6509 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6513 if(typeof callback === 'function')
6517 NETDATA.error(418, NETDATA.registry.server);
6519 if(typeof callback === 'function')
6524 switch: function(new_person_guid, callback) {
6527 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,
6531 'Cache-Control': 'no-cache, no-store',
6532 'Pragma': 'no-cache'
6534 xhrFields: { withCredentials: true } // required for the cookie
6536 .done(function(data) {
6537 if(typeof data.status !== 'string' || data.status !== 'ok') {
6538 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6542 if(typeof callback === 'function')
6546 NETDATA.error(414, NETDATA.registry.server);
6548 if(typeof callback === 'function')
6554 // ----------------------------------------------------------------------------------------------------------------
6557 if(typeof netdataPrepCallback === 'function')
6558 netdataPrepCallback();
6560 NETDATA.errorReset();
6561 NETDATA.loadRequiredCSS(0);
6563 NETDATA._loadjQuery(function() {
6564 NETDATA.loadRequiredJs(0, function() {
6565 if(typeof $().emulateTransitionEnd !== 'function') {
6566 // bootstrap is not available
6567 NETDATA.options.current.show_help = false;
6570 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6571 if(NETDATA.options.debug.main_loop === true)
6572 console.log('starting chart refresh thread');
6579 // window.NETDATA = NETDATA;
6580 // })(window, document);