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?v20161002-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?v20161218-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;
2200 // if the value has not changed, skip DOM update
2201 //if(series.last === value) return;
2204 if(typeof value === 'number') {
2205 var v = Math.abs(value);
2206 s = r = this.legendFormatValue(value);
2208 if(typeof series.last === 'number') {
2209 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2210 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2211 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2213 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2218 series.last = value;
2221 if(series.value !== null) series.value.innerHTML = s;
2222 if(series.user !== null) series.user.innerHTML = r;
2225 this.legendSetDate = function(ms) {
2226 if(typeof ms !== 'number') {
2227 this.legendShowUndefined();
2231 var d = new Date(ms);
2233 if(this.element_legend_childs.title_date)
2234 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2236 if(this.element_legend_childs.title_time)
2237 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2239 if(this.element_legend_childs.title_units)
2240 this.element_legend_childs.title_units.innerHTML = this.units;
2243 this.legendShowUndefined = function() {
2244 if(this.element_legend_childs.title_date)
2245 this.element_legend_childs.title_date.innerHTML = ' ';
2247 if(this.element_legend_childs.title_time)
2248 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2250 if(this.element_legend_childs.title_units)
2251 this.element_legend_childs.title_units.innerHTML = ' ';
2253 if(this.data && this.element_legend_childs.series !== null) {
2254 var labels = this.data.dimension_names;
2255 var i = labels.length;
2257 var label = labels[i];
2259 if(typeof label === 'undefined') continue;
2260 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2261 this.legendSetLabelValue(label, null);
2266 this.legendShowLatestValues = function() {
2267 if(this.chart === null) return;
2268 if(this.selected) return;
2270 if(this.data === null || this.element_legend_childs.series === null) {
2271 this.legendShowUndefined();
2275 var show_undefined = true;
2276 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2277 show_undefined = false;
2279 if(show_undefined) {
2280 this.legendShowUndefined();
2284 this.legendSetDate(this.view_before);
2286 var labels = this.data.dimension_names;
2287 var i = labels.length;
2289 var label = labels[i];
2291 if(typeof label === 'undefined') continue;
2292 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2295 this.legendSetLabelValue(label, null);
2297 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2301 this.legendReset = function() {
2302 this.legendShowLatestValues();
2305 // this should be called just ONCE per dimension per chart
2306 this._chartDimensionColor = function(label) {
2307 if(this.colors === null) this.chartColors();
2309 if(typeof this.colors_assigned[label] === 'undefined') {
2310 if(this.colors_available.length === 0) {
2311 var len = NETDATA.themes.current.colors.length;
2313 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2316 this.colors_assigned[label] = this.colors_available.shift();
2318 if(this.debug === true)
2319 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2322 if(this.debug === true)
2323 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2326 this.colors.push(this.colors_assigned[label]);
2327 return this.colors_assigned[label];
2330 this.chartColors = function() {
2331 if(this.colors !== null) return this.colors;
2333 this.colors = new Array();
2334 this.colors_available = new Array();
2336 // add the standard colors
2337 var len = NETDATA.themes.current.colors.length;
2339 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2341 // add the user supplied colors
2342 var c = $(this.element).data('colors');
2343 // this.log('read colors: ' + c);
2344 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2345 if(typeof c !== 'string') {
2346 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2356 this.colors_available.unshift(c[len]);
2357 // this.log('adding color: ' + c[len]);
2366 this.legendUpdateDOM = function() {
2369 // check that the legend DOM is up to date for the downloaded dimensions
2370 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2371 // this.log('the legend does not have any series - requesting legend update');
2374 else if(this.data === null) {
2375 // this.log('the chart does not have any data - requesting legend update');
2378 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2382 var labels = this.data.dimension_names.toString();
2383 if(labels !== this.element_legend_childs.series.labels_key) {
2386 if(this.debug === true)
2387 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2391 if(needed === false) {
2392 // make sure colors available
2395 // do we have to update the current values?
2396 // we do this, only when the visible chart is current
2397 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2398 if(this.debug === true)
2399 this.log('chart is in latest position... updating values on legend...');
2401 //var labels = this.data.dimension_names;
2402 //var i = labels.length;
2404 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2408 if(this.colors === null) {
2409 // this is the first time we update the chart
2410 // let's assign colors to all dimensions
2411 if(this.library.track_colors() === true)
2412 for(var dim in this.chart.dimensions)
2413 this._chartDimensionColor(this.chart.dimensions[dim].name);
2415 // we will re-generate the colors for the chart
2416 // based on the selected dimensions
2419 if(this.debug === true)
2420 this.log('updating Legend DOM');
2422 // mark all dimensions as invalid
2423 this.dimensions_visibility.invalidateAll();
2425 var genLabel = function(state, parent, dim, name, count) {
2426 var color = state._chartDimensionColor(name);
2428 var user_element = null;
2429 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2430 if(user_id === null)
2431 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2432 if(user_id !== null) {
2433 user_element = document.getElementById(user_id) || null;
2434 if (user_element === null)
2435 state.log('Cannot find element with id: ' + user_id);
2438 state.element_legend_childs.series[name] = {
2439 name: document.createElement('span'),
2440 value: document.createElement('span'),
2445 var label = state.element_legend_childs.series[name];
2447 // create the dimension visibility tracking for this label
2448 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2450 var rgb = NETDATA.colorHex2Rgb(color);
2451 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2452 + state.chart.chart_type
2453 + '" style="background-color: '
2454 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2455 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2457 var text = document.createTextNode(' ' + name);
2458 label.name.appendChild(text);
2461 parent.appendChild(document.createElement('br'));
2463 parent.appendChild(label.name);
2464 parent.appendChild(label.value);
2467 var content = document.createElement('div');
2469 if(this.hasLegend()) {
2470 this.element_legend_childs = {
2472 resize_handler: document.createElement('div'),
2473 toolbox: document.createElement('div'),
2474 toolbox_left: document.createElement('div'),
2475 toolbox_right: document.createElement('div'),
2476 toolbox_reset: document.createElement('div'),
2477 toolbox_zoomin: document.createElement('div'),
2478 toolbox_zoomout: document.createElement('div'),
2479 toolbox_volume: document.createElement('div'),
2480 title_date: document.createElement('span'),
2481 title_time: document.createElement('span'),
2482 title_units: document.createElement('span'),
2483 nano: document.createElement('div'),
2485 paneClass: 'netdata-legend-series-pane',
2486 sliderClass: 'netdata-legend-series-slider',
2487 contentClass: 'netdata-legend-series-content',
2488 enabledClass: '__enabled',
2489 flashedClass: '__flashed',
2490 activeClass: '__active',
2492 alwaysVisible: true,
2498 this.element_legend.innerHTML = '';
2500 if(this.library.toolboxPanAndZoom !== null) {
2502 function get_pan_and_zoom_step(event) {
2504 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2506 else if (event.shiftKey)
2507 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2509 else if (event.altKey)
2510 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2513 return NETDATA.options.current.pan_and_zoom_factor;
2516 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2517 this.element.appendChild(this.element_legend_childs.toolbox);
2519 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2520 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2521 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2522 this.element_legend_childs.toolbox_left.onclick = function(e) {
2525 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2526 var before = that.view_before - step;
2527 var after = that.view_after - step;
2528 if(after >= that.netdata_first)
2529 that.library.toolboxPanAndZoom(that, after, before);
2531 if(NETDATA.options.current.show_help === true)
2532 $(this.element_legend_childs.toolbox_left).popover({
2537 placement: 'bottom',
2538 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2540 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>'
2544 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2545 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2546 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2547 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2549 NETDATA.resetAllCharts(that);
2551 if(NETDATA.options.current.show_help === true)
2552 $(this.element_legend_childs.toolbox_reset).popover({
2557 placement: 'bottom',
2558 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2559 title: 'Chart Reset',
2560 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>'
2563 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2564 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2565 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2566 this.element_legend_childs.toolbox_right.onclick = function(e) {
2568 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2569 var before = that.view_before + step;
2570 var after = that.view_after + step;
2571 if(before <= that.netdata_last)
2572 that.library.toolboxPanAndZoom(that, after, before);
2574 if(NETDATA.options.current.show_help === true)
2575 $(this.element_legend_childs.toolbox_right).popover({
2580 placement: 'bottom',
2581 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2583 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>'
2587 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2588 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2589 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2590 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2592 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2593 var before = that.view_before - dt;
2594 var after = that.view_after + dt;
2595 that.library.toolboxPanAndZoom(that, after, before);
2597 if(NETDATA.options.current.show_help === true)
2598 $(this.element_legend_childs.toolbox_zoomin).popover({
2603 placement: 'bottom',
2604 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2605 title: 'Chart Zoom In',
2606 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>'
2609 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2610 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2611 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2612 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2614 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);
2615 var before = that.view_before + dt;
2616 var after = that.view_after - dt;
2618 that.library.toolboxPanAndZoom(that, after, before);
2620 if(NETDATA.options.current.show_help === true)
2621 $(this.element_legend_childs.toolbox_zoomout).popover({
2626 placement: 'bottom',
2627 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2628 title: 'Chart Zoom Out',
2629 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>'
2632 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2633 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2634 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2635 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2636 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2637 //e.preventDefault();
2638 //alert('clicked toolbox_volume on ' + that.id);
2642 this.element_legend_childs.toolbox = null;
2643 this.element_legend_childs.toolbox_left = null;
2644 this.element_legend_childs.toolbox_reset = null;
2645 this.element_legend_childs.toolbox_right = null;
2646 this.element_legend_childs.toolbox_zoomin = null;
2647 this.element_legend_childs.toolbox_zoomout = null;
2648 this.element_legend_childs.toolbox_volume = null;
2651 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2652 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2653 this.element.appendChild(this.element_legend_childs.resize_handler);
2654 if(NETDATA.options.current.show_help === true)
2655 $(this.element_legend_childs.resize_handler).popover({
2660 placement: 'bottom',
2661 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2662 title: 'Chart Resize',
2663 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>'
2667 this.element_legend_childs.resize_handler.onmousedown =
2669 that.resizeHandler(e);
2673 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2674 that.resizeHandler(e);
2677 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2678 this.element_legend.appendChild(this.element_legend_childs.title_date);
2680 this.element_legend.appendChild(document.createElement('br'));
2682 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2683 this.element_legend.appendChild(this.element_legend_childs.title_time);
2685 this.element_legend.appendChild(document.createElement('br'));
2687 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2688 this.element_legend.appendChild(this.element_legend_childs.title_units);
2690 this.element_legend.appendChild(document.createElement('br'));
2692 this.element_legend_childs.nano.className = 'netdata-legend-series';
2693 this.element_legend.appendChild(this.element_legend_childs.nano);
2695 content.className = 'netdata-legend-series-content';
2696 this.element_legend_childs.nano.appendChild(content);
2698 if(NETDATA.options.current.show_help === true)
2699 $(content).popover({
2704 placement: 'bottom',
2705 title: 'Chart Legend',
2706 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2707 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>'
2711 this.element_legend_childs = {
2713 resize_handler: null,
2716 toolbox_right: null,
2717 toolbox_reset: null,
2718 toolbox_zoomin: null,
2719 toolbox_zoomout: null,
2720 toolbox_volume: null,
2731 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2732 if(this.debug === true)
2733 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2735 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2736 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2740 var tmp = new Array();
2741 for(var dim in this.chart.dimensions) {
2742 tmp.push(this.chart.dimensions[dim].name);
2743 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2745 this.element_legend_childs.series.labels_key = tmp.toString();
2746 if(this.debug === true)
2747 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2750 // create a hidden div to be used for hidding
2751 // the original legend of the chart library
2752 var el = document.createElement('div');
2753 if(this.element_legend !== null)
2754 this.element_legend.appendChild(el);
2755 el.style.display = 'none';
2757 this.element_legend_childs.hidden = document.createElement('div');
2758 el.appendChild(this.element_legend_childs.hidden);
2760 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2761 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2763 this.legendShowLatestValues();
2766 this.hasLegend = function() {
2767 if(typeof this.___hasLegendCache___ !== 'undefined')
2768 return this.___hasLegendCache___;
2771 if(this.library && this.library.legend(this) === 'right-side') {
2772 var legend = $(this.element).data('legend') || 'yes';
2773 if(legend === 'yes') leg = true;
2776 this.___hasLegendCache___ = leg;
2780 this.legendWidth = function() {
2781 return (this.hasLegend())?140:0;
2784 this.legendHeight = function() {
2785 return $(this.element).height();
2788 this.chartWidth = function() {
2789 return $(this.element).width() - this.legendWidth();
2792 this.chartHeight = function() {
2793 return $(this.element).height();
2796 this.chartPixelsPerPoint = function() {
2797 // force an options provided detail
2798 var px = this.pixels_per_point;
2800 if(this.library && px < this.library.pixels_per_point(this))
2801 px = this.library.pixels_per_point(this);
2803 if(px < NETDATA.options.current.pixels_per_point)
2804 px = NETDATA.options.current.pixels_per_point;
2809 this.needsRecreation = function() {
2811 this.chart_created === true
2813 && this.library.autoresize() === false
2814 && this.tm.last_resized < NETDATA.options.last_resized
2818 this.chartURL = function() {
2819 var after, before, points_multiplier = 1;
2820 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2821 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2823 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2824 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2825 this.view_after = after * 1000;
2826 this.view_before = before * 1000;
2828 this.requested_padding = null;
2829 points_multiplier = 1;
2831 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2832 this.tm.pan_and_zoom_seq = 0;
2834 before = Math.round(this.current.force_before_ms / 1000);
2835 after = Math.round(this.current.force_after_ms / 1000);
2836 this.view_after = after * 1000;
2837 this.view_before = before * 1000;
2839 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2840 this.requested_padding = Math.round((before - after) / 2);
2841 after -= this.requested_padding;
2842 before += this.requested_padding;
2843 this.requested_padding *= 1000;
2844 points_multiplier = 2;
2847 this.current.force_before_ms = null;
2848 this.current.force_after_ms = null;
2851 this.tm.pan_and_zoom_seq = 0;
2853 before = this.before;
2855 this.view_after = after * 1000;
2856 this.view_before = before * 1000;
2858 this.requested_padding = null;
2859 points_multiplier = 1;
2862 this.requested_after = after * 1000;
2863 this.requested_before = before * 1000;
2865 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2867 // build the data URL
2868 this.data_url = this.host + this.chart.data_url;
2869 this.data_url += "&format=" + this.library.format();
2870 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2871 this.data_url += "&group=" + this.method;
2873 if(this.override_options !== null)
2874 this.data_url += "&options=" + this.override_options.toString();
2876 this.data_url += "&options=" + this.library.options(this);
2878 this.data_url += '|jsonwrap';
2880 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2881 this.data_url += '|nonzero';
2883 if(this.append_options !== null)
2884 this.data_url += '|' + this.append_options.toString();
2887 this.data_url += "&after=" + after.toString();
2890 this.data_url += "&before=" + before.toString();
2893 this.data_url += "&dimensions=" + this.dimensions;
2895 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2896 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2899 this.redrawChart = function() {
2900 if(this.data !== null)
2901 this.updateChartWithData(this.data);
2904 this.updateChartWithData = function(data) {
2905 if(this.debug === true)
2906 this.log('updateChartWithData() called.');
2908 // this may force the chart to be re-created
2912 this.updates_counter++;
2913 this.updates_since_last_unhide++;
2914 this.updates_since_last_creation++;
2916 var started = Date.now();
2918 // if the result is JSON, find the latest update-every
2919 this.data_update_every = data.view_update_every * 1000;
2920 this.data_after = data.after * 1000;
2921 this.data_before = data.before * 1000;
2922 this.netdata_first = data.first_entry * 1000;
2923 this.netdata_last = data.last_entry * 1000;
2924 this.data_points = data.points;
2927 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2928 if(this.view_after < this.data_after) {
2929 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2930 this.view_after = this.data_after;
2933 if(this.view_before > this.data_before) {
2934 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2935 this.view_before = this.data_before;
2939 this.view_after = this.data_after;
2940 this.view_before = this.data_before;
2943 if(this.debug === true) {
2944 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2946 if(this.current.force_after_ms)
2947 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2949 this.log('STATUS: forced : unset');
2951 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2952 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2953 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2954 this.log('STATUS: points : ' + (this.data_points).toString());
2957 if(this.data_points === 0) {
2962 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2963 if(this.debug === true)
2964 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2966 this.chart_created = false;
2969 // check and update the legend
2970 this.legendUpdateDOM();
2972 if(this.chart_created === true
2973 && typeof this.library.update === 'function') {
2975 if(this.debug === true)
2976 this.log('updating chart...');
2978 if(callChartLibraryUpdateSafely(data) === false)
2982 if(this.debug === true)
2983 this.log('creating chart...');
2985 if(callChartLibraryCreateSafely(data) === false)
2989 this.legendShowLatestValues();
2990 if(this.selected === true)
2991 NETDATA.globalSelectionSync.stop();
2993 // update the performance counters
2994 var now = Date.now();
2995 this.tm.last_updated = now;
2997 // don't update last_autorefreshed if this chart is
2998 // forced to be updated with global PanAndZoom
2999 if(NETDATA.globalPanAndZoom.isActive())
3000 this.tm.last_autorefreshed = 0;
3002 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3003 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3005 this.tm.last_autorefreshed = now;
3008 this.refresh_dt_ms = now - started;
3009 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3011 if(this.refresh_dt_element !== null)
3012 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3015 this.updateChart = function(callback) {
3016 if(this.debug === true)
3017 this.log('updateChart() called.');
3019 if(this._updating === true) {
3020 if(this.debug === true)
3021 this.log('I am already updating...');
3023 if(typeof callback === 'function') callback();
3027 // due to late initialization of charts and libraries
3028 // we need to check this too
3029 if(this.enabled === false) {
3030 if(this.debug === true)
3031 this.log('I am not enabled');
3033 if(typeof callback === 'function') callback();
3037 if(canBeRendered() === false) {
3038 if(typeof callback === 'function') callback();
3042 if(this.chart === null) {
3043 this.getChart(function() { that.updateChart(callback); });
3047 if(this.library.initialized === false) {
3048 if(this.library.enabled === true) {
3049 this.library.initialize(function() { that.updateChart(callback); });
3053 error('chart library "' + this.library_name + '" is not available.');
3054 if(typeof callback === 'function') callback();
3059 this.clearSelection();
3062 if(this.debug === true)
3063 this.log('updating from ' + this.data_url);
3065 NETDATA.statistics.refreshes_total++;
3066 NETDATA.statistics.refreshes_active++;
3068 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3069 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3071 this._updating = true;
3073 this.xhr = $.ajax( {
3078 'Cache-Control': 'no-cache, no-store',
3079 'Pragma': 'no-cache'
3081 xhrFields: { withCredentials: true } // required for the cookie
3083 .done(function(data) {
3084 that.xhr = undefined;
3085 that.retries_on_data_failures = 0;
3087 if(that.debug === true)
3088 that.log('data received. updating chart.');
3090 that.updateChartWithData(data);
3092 .fail(function(msg) {
3093 that.xhr = undefined;
3095 if(msg.statusText !== 'abort') {
3096 that.retries_on_data_failures++;
3097 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3098 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3099 that.retries_on_data_failures = 0;
3100 error('data download failed for url: ' + that.data_url);
3103 that.tm.last_autorefreshed = Date.now();
3104 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3108 .always(function() {
3109 that.xhr = undefined;
3111 NETDATA.statistics.refreshes_active--;
3112 that._updating = false;
3113 if(typeof callback === 'function') callback();
3119 this.isVisible = function(nocache) {
3120 if(typeof nocache === 'undefined')
3123 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3125 // caching - we do not evaluate the charts visibility
3126 // if the page has not been scrolled since the last check
3127 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3128 return this.___isVisible___;
3130 this.tm.last_visible_check = Date.now();
3132 var wh = window.innerHeight;
3133 var x = this.element.getBoundingClientRect();
3137 if(x.width === 0 || x.height === 0) {
3139 this.___isVisible___ = false;
3140 return this.___isVisible___;
3143 if(x.top < 0 && -x.top > x.height) {
3144 // the chart is entirely above
3145 ret = -x.top - x.height;
3147 else if(x.top > wh) {
3148 // the chart is entirely below
3152 if(ret > tolerance) {
3153 // the chart is too far
3156 this.___isVisible___ = false;
3157 return this.___isVisible___;
3160 // the chart is inside or very close
3163 this.___isVisible___ = true;
3164 return this.___isVisible___;
3168 this.isAutoRefreshable = function() {
3169 return (this.current.autorefresh);
3172 this.canBeAutoRefreshed = function() {
3173 var now = Date.now();
3175 if(this.running === true) {
3176 if(this.debug === true)
3177 this.log('I am already running');
3182 if(this.enabled === false) {
3183 if(this.debug === true)
3184 this.log('I am not enabled');
3189 if(this.library === null || this.library.enabled === false) {
3190 error('charting library "' + this.library_name + '" is not available');
3191 if(this.debug === true)
3192 this.log('My chart library ' + this.library_name + ' is not available');
3197 if(this.isVisible() === false) {
3198 if(NETDATA.options.debug.visibility === true || this.debug === true)
3199 this.log('I am not visible');
3204 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3205 if(this.debug === true)
3206 this.log('timed force update detected - allowing this update');
3208 this.current.force_update_at = 0;
3212 if(this.isAutoRefreshable() === true) {
3213 // allow the first update, even if the page is not visible
3214 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3215 if(NETDATA.options.debug.focus === true || this.debug === true)
3216 this.log('canBeAutoRefreshed(): page does not have focus');
3221 if(this.needsRecreation() === true) {
3222 if(this.debug === true)
3223 this.log('canBeAutoRefreshed(): needs re-creation.');
3228 // options valid only for autoRefresh()
3229 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3230 if(NETDATA.globalPanAndZoom.isActive()) {
3231 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3232 if(this.debug === true)
3233 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3238 if(this.debug === true)
3239 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3245 if(this.selected === true) {
3246 if(this.debug === true)
3247 this.log('canBeAutoRefreshed(): I have a selection in place.');
3252 if(this.paused === true) {
3253 if(this.debug === true)
3254 this.log('canBeAutoRefreshed(): I am paused.');
3259 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3260 if(this.debug === true)
3261 this.log('canBeAutoRefreshed(): It is time to update me.');
3271 this.autoRefresh = function(callback) {
3272 if(this.canBeAutoRefreshed() === true && this.running === false) {
3275 state.running = true;
3276 state.updateChart(function() {
3277 state.running = false;
3279 if(typeof callback !== 'undefined')
3284 if(typeof callback !== 'undefined')
3289 this._defaultsFromDownloadedChart = function(chart) {
3291 this.chart_url = chart.url;
3292 this.data_update_every = chart.update_every * 1000;
3293 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3294 this.tm.last_info_downloaded = Date.now();
3296 if(this.title === null)
3297 this.title = chart.title;
3299 if(this.units === null)
3300 this.units = chart.units;
3303 // fetch the chart description from the netdata server
3304 this.getChart = function(callback) {
3305 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3307 this._defaultsFromDownloadedChart(this.chart);
3308 if(typeof callback === 'function') callback();
3311 this.chart_url = "/api/v1/chart?chart=" + this.id;
3313 if(this.debug === true)
3314 this.log('downloading ' + this.chart_url);
3317 url: this.host + this.chart_url,
3320 xhrFields: { withCredentials: true } // required for the cookie
3322 .done(function(chart) {
3323 chart.url = that.chart_url;
3324 that._defaultsFromDownloadedChart(chart);
3325 NETDATA.chartRegistry.add(that.host, that.id, chart);
3328 NETDATA.error(404, that.chart_url);
3329 error('chart not found on url "' + that.chart_url + '"');
3331 .always(function() {
3332 if(typeof callback === 'function') callback();
3337 // ============================================================================================================
3343 NETDATA.resetAllCharts = function(state) {
3344 // first clear the global selection sync
3345 // to make sure no chart is in selected state
3346 state.globalSelectionSyncStop();
3348 // there are 2 possibilities here
3349 // a. state is the global Pan and Zoom master
3350 // b. state is not the global Pan and Zoom master
3352 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3355 // clear the global Pan and Zoom
3356 // this will also refresh the master
3357 // and unblock any charts currently mirroring the master
3358 NETDATA.globalPanAndZoom.clearMaster();
3360 // if we were not the master, reset our status too
3361 // this is required because most probably the mouse
3362 // is over this chart, blocking it from auto-refreshing
3363 if(master === false && (state.paused === true || state.selected === true))
3367 // get or create a chart state, given a DOM element
3368 NETDATA.chartState = function(element) {
3369 var state = $(element).data('netdata-state-object') || null;
3370 if(state === null) {
3371 state = new chartState(element);
3372 $(element).data('netdata-state-object', state);
3377 // ----------------------------------------------------------------------------------------------------------------
3378 // Library functions
3380 // Load a script without jquery
3381 // This is used to load jquery - after it is loaded, we use jquery
3382 NETDATA._loadjQuery = function(callback) {
3383 if(typeof jQuery === 'undefined') {
3384 if(NETDATA.options.debug.main_loop === true)
3385 console.log('loading ' + NETDATA.jQuery);
3387 var script = document.createElement('script');
3388 script.type = 'text/javascript';
3389 script.async = true;
3390 script.src = NETDATA.jQuery;
3392 // script.onabort = onError;
3393 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3394 if(typeof callback === "function")
3395 script.onload = callback;
3397 var s = document.getElementsByTagName('script')[0];
3398 s.parentNode.insertBefore(script, s);
3400 else if(typeof callback === "function")
3404 NETDATA._loadCSS = function(filename) {
3405 // don't use jQuery here
3406 // styles are loaded before jQuery
3407 // to eliminate showing an unstyled page to the user
3409 var fileref = document.createElement("link");
3410 fileref.setAttribute("rel", "stylesheet");
3411 fileref.setAttribute("type", "text/css");
3412 fileref.setAttribute("href", filename);
3414 if (typeof fileref !== 'undefined')
3415 document.getElementsByTagName("head")[0].appendChild(fileref);
3418 NETDATA.colorHex2Rgb = function(hex) {
3419 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3420 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3421 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3422 return r + r + g + g + b + b;
3425 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3427 r: parseInt(result[1], 16),
3428 g: parseInt(result[2], 16),
3429 b: parseInt(result[3], 16)
3433 NETDATA.colorLuminance = function(hex, lum) {
3434 // validate hex string
3435 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3437 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3441 // convert to decimal and change luminosity
3442 var rgb = "#", c, i;
3443 for (i = 0; i < 3; i++) {
3444 c = parseInt(hex.substr(i*2,2), 16);
3445 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3446 rgb += ("00"+c).substr(c.length);
3452 NETDATA.guid = function() {
3454 return Math.floor((1 + Math.random()) * 0x10000)
3459 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3462 NETDATA.zeropad = function(x) {
3463 if(x > -10 && x < 10) return '0' + x.toString();
3464 else return x.toString();
3467 // user function to signal us the DOM has been
3469 NETDATA.updatedDom = function() {
3470 NETDATA.options.updated_dom = true;
3473 NETDATA.ready = function(callback) {
3474 NETDATA.options.pauseCallback = callback;
3477 NETDATA.pause = function(callback) {
3478 if(NETDATA.options.pause === true)
3481 NETDATA.options.pauseCallback = callback;
3484 NETDATA.unpause = function() {
3485 NETDATA.options.pauseCallback = null;
3486 NETDATA.options.updated_dom = true;
3487 NETDATA.options.pause = false;
3490 // ----------------------------------------------------------------------------------------------------------------
3492 // this is purely sequencial charts refresher
3493 // it is meant to be autonomous
3494 NETDATA.chartRefresherNoParallel = function(index) {
3495 if(NETDATA.options.debug.mail_loop === true)
3496 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3498 if(NETDATA.options.updated_dom === true) {
3499 // the dom has been updated
3500 // get the dom parts again
3501 NETDATA.parseDom(NETDATA.chartRefresher);
3504 if(index >= NETDATA.options.targets.length) {
3505 if(NETDATA.options.debug.main_loop === true)
3506 console.log('waiting to restart main loop...');
3508 NETDATA.options.auto_refresher_fast_weight = 0;
3510 setTimeout(function() {
3511 NETDATA.chartRefresher();
3512 }, NETDATA.options.current.idle_between_loops);
3515 var state = NETDATA.options.targets[index];
3517 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3518 if(NETDATA.options.debug.main_loop === true)
3519 console.log('fast rendering...');
3521 state.autoRefresh(function() {
3522 NETDATA.chartRefresherNoParallel(++index);
3526 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3527 NETDATA.options.auto_refresher_fast_weight = 0;
3529 setTimeout(function() {
3530 state.autoRefresh(function() {
3531 NETDATA.chartRefresherNoParallel(++index);
3533 }, NETDATA.options.current.idle_between_charts);
3538 // this is part of the parallel refresher
3539 // its cause is to refresh sequencially all the charts
3540 // that depend on chart library initialization
3541 // it will call the parallel refresher back
3542 // as soon as it sees a chart that its chart library
3544 NETDATA.chartRefresher_uninitialized = function() {
3545 if(NETDATA.options.updated_dom === true) {
3546 // the dom has been updated
3547 // get the dom parts again
3548 NETDATA.parseDom(NETDATA.chartRefresher);
3552 if(NETDATA.options.sequencial.length === 0)
3553 NETDATA.chartRefresher();
3555 var state = NETDATA.options.sequencial.pop();
3556 if(state.library.initialized === true)
3557 NETDATA.chartRefresher();
3559 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3563 NETDATA.chartRefresherWaitTime = function() {
3564 return NETDATA.options.current.idle_parallel_loops;
3567 // the default refresher
3568 // it will create 2 sets of charts:
3569 // - the ones that can be refreshed in parallel
3570 // - the ones that depend on something else
3571 // the first set will be executed in parallel
3572 // the second will be given to NETDATA.chartRefresher_uninitialized()
3573 NETDATA.chartRefresher = function() {
3574 // console.log('auto-refresher...');
3576 if(NETDATA.options.pause === true) {
3577 // console.log('auto-refresher is paused');
3578 setTimeout(NETDATA.chartRefresher,
3579 NETDATA.chartRefresherWaitTime());
3583 if(typeof NETDATA.options.pauseCallback === 'function') {
3584 // console.log('auto-refresher is calling pauseCallback');
3585 NETDATA.options.pause = true;
3586 NETDATA.options.pauseCallback();
3587 NETDATA.chartRefresher();
3591 if(NETDATA.options.current.parallel_refresher === false) {
3592 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3593 NETDATA.chartRefresherNoParallel(0);
3597 if(NETDATA.options.updated_dom === true) {
3598 // the dom has been updated
3599 // get the dom parts again
3600 // console.log('auto-refresher is calling parseDom()');
3601 NETDATA.parseDom(NETDATA.chartRefresher);
3605 var parallel = new Array();
3606 var targets = NETDATA.options.targets;
3607 var len = targets.length;
3610 state = targets[len];
3611 if(state.isVisible() === false || state.running === true)
3614 if(state.library.initialized === false) {
3615 if(state.library.enabled === true) {
3616 state.library.initialize(NETDATA.chartRefresher);
3620 state.error('chart library "' + state.library_name + '" is not enabled.');
3624 parallel.unshift(state);
3627 if(parallel.length > 0) {
3628 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3629 // this will execute the jobs in parallel
3630 $(parallel).each(function() {
3635 // console.log('auto-refresher nothing to do');
3638 // run the next refresh iteration
3639 setTimeout(NETDATA.chartRefresher,
3640 NETDATA.chartRefresherWaitTime());
3643 NETDATA.parseDom = function(callback) {
3644 NETDATA.options.last_page_scroll = Date.now();
3645 NETDATA.options.updated_dom = false;
3647 var targets = $('div[data-netdata]'); //.filter(':visible');
3649 if(NETDATA.options.debug.main_loop === true)
3650 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3652 NETDATA.options.targets = new Array();
3653 var len = targets.length;
3655 // the initialization will take care of sizing
3656 // and the "loading..." message
3657 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3660 if(typeof callback === 'function') callback();
3663 // this is the main function - where everything starts
3664 NETDATA.start = function() {
3665 // this should be called only once
3667 NETDATA.options.page_is_visible = true;
3669 $(window).blur(function() {
3670 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3671 NETDATA.options.page_is_visible = false;
3672 if(NETDATA.options.debug.focus === true)
3673 console.log('Lost Focus!');
3677 $(window).focus(function() {
3678 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3679 NETDATA.options.page_is_visible = true;
3680 if(NETDATA.options.debug.focus === true)
3681 console.log('Focus restored!');
3685 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3686 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3687 NETDATA.options.page_is_visible = false;
3688 if(NETDATA.options.debug.focus === true)
3689 console.log('Document has no focus!');
3693 // bootstrap tab switching
3694 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3696 // bootstrap modal switching
3697 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3698 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3700 // bootstrap collapse switching
3701 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3702 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3704 NETDATA.parseDom(NETDATA.chartRefresher);
3706 // Alarms initialization
3707 setTimeout(NETDATA.alarms.init, 1000);
3709 // Registry initialization
3710 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3712 if(typeof netdataCallback === 'function')
3716 // ----------------------------------------------------------------------------------------------------------------
3719 NETDATA.peityInitialize = function(callback) {
3720 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3722 url: NETDATA.peity_js,
3725 xhrFields: { withCredentials: true } // required for the cookie
3728 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3731 NETDATA.chartLibraries.peity.enabled = false;
3732 NETDATA.error(100, NETDATA.peity_js);
3734 .always(function() {
3735 if(typeof callback === "function")
3740 NETDATA.chartLibraries.peity.enabled = false;
3741 if(typeof callback === "function")
3746 NETDATA.peityChartUpdate = function(state, data) {
3747 state.peity_instance.innerHTML = data.result;
3749 if(state.peity_options.stroke !== state.chartColors()[0]) {
3750 state.peity_options.stroke = state.chartColors()[0];
3751 if(state.chart.chart_type === 'line')
3752 state.peity_options.fill = NETDATA.themes.current.background;
3754 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3757 $(state.peity_instance).peity('line', state.peity_options);
3761 NETDATA.peityChartCreate = function(state, data) {
3762 state.peity_instance = document.createElement('div');
3763 state.element_chart.appendChild(state.peity_instance);
3765 var self = $(state.element);
3766 state.peity_options = {
3767 stroke: NETDATA.themes.current.foreground,
3768 strokeWidth: self.data('peity-strokewidth') || 1,
3769 width: state.chartWidth(),
3770 height: state.chartHeight(),
3771 fill: NETDATA.themes.current.foreground
3774 NETDATA.peityChartUpdate(state, data);
3778 // ----------------------------------------------------------------------------------------------------------------
3781 NETDATA.sparklineInitialize = function(callback) {
3782 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3784 url: NETDATA.sparkline_js,
3787 xhrFields: { withCredentials: true } // required for the cookie
3790 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3793 NETDATA.chartLibraries.sparkline.enabled = false;
3794 NETDATA.error(100, NETDATA.sparkline_js);
3796 .always(function() {
3797 if(typeof callback === "function")
3802 NETDATA.chartLibraries.sparkline.enabled = false;
3803 if(typeof callback === "function")
3808 NETDATA.sparklineChartUpdate = function(state, data) {
3809 state.sparkline_options.width = state.chartWidth();
3810 state.sparkline_options.height = state.chartHeight();
3812 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3816 NETDATA.sparklineChartCreate = function(state, data) {
3817 var self = $(state.element);
3818 var type = self.data('sparkline-type') || 'line';
3819 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3820 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3821 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3822 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3823 var composite = self.data('sparkline-composite') || undefined;
3824 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3825 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3826 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3827 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3828 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3829 var spotColor = self.data('sparkline-spotcolor') || undefined;
3830 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3831 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3832 var spotRadius = self.data('sparkline-spotradius') || undefined;
3833 var valueSpots = self.data('sparkline-valuespots') || undefined;
3834 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3835 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3836 var lineWidth = self.data('sparkline-linewidth') || undefined;
3837 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3838 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3839 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3840 var xvalues = self.data('sparkline-xvalues') || undefined;
3841 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3842 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3843 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3844 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3845 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3846 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3847 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3848 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3849 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3850 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3851 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3852 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3853 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3854 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3855 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3856 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3857 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3858 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3859 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3860 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3861 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3862 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3864 if(spotColor === 'disable') spotColor='';
3865 if(minSpotColor === 'disable') minSpotColor='';
3866 if(maxSpotColor === 'disable') maxSpotColor='';
3868 state.sparkline_options = {
3870 lineColor: lineColor,
3871 fillColor: fillColor,
3872 chartRangeMin: chartRangeMin,
3873 chartRangeMax: chartRangeMax,
3874 composite: composite,
3875 enableTagOptions: enableTagOptions,
3876 tagOptionPrefix: tagOptionPrefix,
3877 tagValuesAttribute: tagValuesAttribute,
3878 disableHiddenCheck: disableHiddenCheck,
3879 defaultPixelsPerValue: defaultPixelsPerValue,
3880 spotColor: spotColor,
3881 minSpotColor: minSpotColor,
3882 maxSpotColor: maxSpotColor,
3883 spotRadius: spotRadius,
3884 valueSpots: valueSpots,
3885 highlightSpotColor: highlightSpotColor,
3886 highlightLineColor: highlightLineColor,
3887 lineWidth: lineWidth,
3888 normalRangeMin: normalRangeMin,
3889 normalRangeMax: normalRangeMax,
3890 drawNormalOnTop: drawNormalOnTop,
3892 chartRangeClip: chartRangeClip,
3893 chartRangeMinX: chartRangeMinX,
3894 chartRangeMaxX: chartRangeMaxX,
3895 disableInteraction: disableInteraction,
3896 disableTooltips: disableTooltips,
3897 disableHighlight: disableHighlight,
3898 highlightLighten: highlightLighten,
3899 highlightColor: highlightColor,
3900 tooltipContainer: tooltipContainer,
3901 tooltipClassname: tooltipClassname,
3902 tooltipChartTitle: state.title,
3903 tooltipFormat: tooltipFormat,
3904 tooltipPrefix: tooltipPrefix,
3905 tooltipSuffix: tooltipSuffix,
3906 tooltipSkipNull: tooltipSkipNull,
3907 tooltipValueLookups: tooltipValueLookups,
3908 tooltipFormatFieldlist: tooltipFormatFieldlist,
3909 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3910 numberFormatter: numberFormatter,
3911 numberDigitGroupSep: numberDigitGroupSep,
3912 numberDecimalMark: numberDecimalMark,
3913 numberDigitGroupCount: numberDigitGroupCount,
3914 animatedZooms: animatedZooms,
3915 width: state.chartWidth(),
3916 height: state.chartHeight()
3919 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3923 // ----------------------------------------------------------------------------------------------------------------
3930 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3931 if(after < state.netdata_first)
3932 after = state.netdata_first;
3934 if(before > state.netdata_last)
3935 before = state.netdata_last;
3937 state.setMode('zoom');
3938 state.globalSelectionSyncStop();
3939 state.globalSelectionSyncDelay();
3940 state.dygraph_user_action = true;
3941 state.dygraph_force_zoom = true;
3942 state.updateChartPanOrZoom(after, before);
3943 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3946 NETDATA.dygraphSetSelection = function(state, t) {
3947 if(typeof state.dygraph_instance !== 'undefined') {
3948 var r = state.calculateRowForTime(t);
3950 state.dygraph_instance.setSelection(r);
3952 state.dygraph_instance.clearSelection();
3953 state.legendShowUndefined();
3960 NETDATA.dygraphClearSelection = function(state, t) {
3961 if(typeof state.dygraph_instance !== 'undefined') {
3962 state.dygraph_instance.clearSelection();
3967 NETDATA.dygraphSmoothInitialize = function(callback) {
3969 url: NETDATA.dygraph_smooth_js,
3972 xhrFields: { withCredentials: true } // required for the cookie
3975 NETDATA.dygraph.smooth = true;
3976 smoothPlotter.smoothing = 0.3;
3979 NETDATA.dygraph.smooth = false;
3981 .always(function() {
3982 if(typeof callback === "function")
3987 NETDATA.dygraphInitialize = function(callback) {
3988 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3990 url: NETDATA.dygraph_js,
3993 xhrFields: { withCredentials: true } // required for the cookie
3996 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3999 NETDATA.chartLibraries.dygraph.enabled = false;
4000 NETDATA.error(100, NETDATA.dygraph_js);
4002 .always(function() {
4003 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4004 NETDATA.dygraphSmoothInitialize(callback);
4005 else if(typeof callback === "function")
4010 NETDATA.chartLibraries.dygraph.enabled = false;
4011 if(typeof callback === "function")
4016 NETDATA.dygraphChartUpdate = function(state, data) {
4017 var dygraph = state.dygraph_instance;
4019 if(typeof dygraph === 'undefined')
4020 return NETDATA.dygraphChartCreate(state, data);
4022 // when the chart is not visible, and hidden
4023 // if there is a window resize, dygraph detects
4024 // its element size as 0x0.
4025 // this will make it re-appear properly
4027 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4031 file: data.result.data,
4032 colors: state.chartColors(),
4033 labels: data.result.labels,
4034 labelsDivWidth: state.chartWidth() - 70,
4035 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4038 if(state.dygraph_force_zoom === true) {
4039 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4040 state.log('dygraphChartUpdate() forced zoom update');
4042 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4043 options.isZoomedIgnoreProgrammaticZoom = true;
4044 state.dygraph_force_zoom = false;
4046 else if(state.current.name !== 'auto') {
4047 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4048 state.log('dygraphChartUpdate() loose update');
4051 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4052 state.log('dygraphChartUpdate() strict update');
4054 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4055 options.isZoomedIgnoreProgrammaticZoom = true;
4058 options.valueRange = state.dygraph_options.valueRange;
4060 var oldMax = null, oldMin = null;
4061 if(state.__commonMin !== null) {
4062 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4063 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4065 if(state.__commonMax !== null) {
4066 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4067 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4070 if(state.dygraph_smooth_eligible === true) {
4071 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4072 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4073 NETDATA.dygraphChartCreate(state, data);
4078 dygraph.updateOptions(options);
4081 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4082 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4083 options.valueRange[0] = NETDATA.commonMin.get(state);
4086 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4087 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4088 options.valueRange[1] = NETDATA.commonMax.get(state);
4092 if(redraw === true) {
4093 // state.log('forcing redraw to adapt to common- min/max');
4094 dygraph.updateOptions(options);
4097 state.dygraph_last_rendered = Date.now();
4101 NETDATA.dygraphChartCreate = function(state, data) {
4102 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4103 state.log('dygraphChartCreate()');
4105 var self = $(state.element);
4107 var chart_type = state.chart.chart_type;
4108 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4109 chart_type = self.data('dygraph-type') || chart_type;
4111 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4112 smooth = self.data('dygraph-smooth') || smooth;
4114 if(NETDATA.dygraph.smooth === false)
4117 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4118 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4120 state.dygraph_options = {
4121 colors: self.data('dygraph-colors') || state.chartColors(),
4123 // leave a few pixels empty on the right of the chart
4124 rightGap: self.data('dygraph-rightgap') || 5,
4125 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4126 showRoller: self.data('dygraph-showroller') || false,
4128 title: self.data('dygraph-title') || state.title,
4129 titleHeight: self.data('dygraph-titleheight') || 19,
4131 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4132 labels: data.result.labels,
4133 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4134 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4135 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4136 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4137 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4140 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4141 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4143 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4144 xRangePad: self.data('dygraph-xrangepad') || 0,
4145 yRangePad: self.data('dygraph-yrangepad') || 1,
4147 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4149 ylabel: state.units,
4150 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4152 // the function to plot the chart
4155 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4156 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4157 strokePattern: self.data('dygraph-strokepattern') || undefined,
4159 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4160 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4161 drawPoints: self.data('dygraph-drawpoints') || false,
4163 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4164 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4166 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4167 pointSize: self.data('dygraph-pointsize') || 1,
4169 // enabling this makes the chart with little square lines
4170 stepPlot: self.data('dygraph-stepplot') || false,
4172 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4173 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4174 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4176 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4177 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4178 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4179 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4181 drawAxis: self.data('dygraph-drawaxis') || true,
4182 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4183 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4184 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4186 drawGrid: self.data('dygraph-drawgrid') || true,
4187 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4188 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4189 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4191 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4192 sigFigs: self.data('dygraph-sigfigs') || null,
4193 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4194 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4196 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4197 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4198 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4200 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4201 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4205 ticker: Dygraph.dateTicker,
4206 axisLabelFormatter: function (d, gran) {
4207 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4209 valueFormatter: function (ms) {
4210 var d = new Date(ms);
4211 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4212 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4217 valueFormatter: function (x) {
4218 // we format legends with the state object
4219 // no need to do anything here
4220 // return (Math.round(x*100) / 100).toLocaleString();
4221 // return state.legendFormatValue(x);
4226 legendFormatter: function(data) {
4227 var elements = state.element_legend_childs;
4229 // if the hidden div is not there
4230 // we are not managing the legend
4231 if(elements.hidden === null) return;
4233 if (typeof data.x !== 'undefined') {
4234 state.legendSetDate(data.x);
4235 var i = data.series.length;
4237 var series = data.series[i];
4238 if(!series.isVisible) continue;
4239 state.legendSetLabelValue(series.label, series.y);
4245 drawCallback: function(dygraph, is_initial) {
4246 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4247 state.dygraph_user_action = false;
4249 var x_range = dygraph.xAxisRange();
4250 var after = Math.round(x_range[0]);
4251 var before = Math.round(x_range[1]);
4253 if(NETDATA.options.debug.dygraph === true)
4254 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4256 if(before <= state.netdata_last && after >= state.netdata_first)
4257 state.updateChartPanOrZoom(after, before);
4260 zoomCallback: function(minDate, maxDate, yRanges) {
4261 if(NETDATA.options.debug.dygraph === true)
4262 state.log('dygraphZoomCallback()');
4264 state.globalSelectionSyncStop();
4265 state.globalSelectionSyncDelay();
4266 state.setMode('zoom');
4268 // refresh it to the greatest possible zoom level
4269 state.dygraph_user_action = true;
4270 state.dygraph_force_zoom = true;
4271 state.updateChartPanOrZoom(minDate, maxDate);
4273 highlightCallback: function(event, x, points, row, seriesName) {
4274 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4275 state.log('dygraphHighlightCallback()');
4279 // there is a bug in dygraph when the chart is zoomed enough
4280 // the time it thinks is selected is wrong
4281 // here we calculate the time t based on the row number selected
4283 var t = state.data_after + row * state.data_update_every;
4284 // 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);
4286 state.globalSelectionSync(x);
4288 // fix legend zIndex using the internal structures of dygraph legend module
4289 // this works, but it is a hack!
4290 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4292 unhighlightCallback: function(event) {
4293 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4294 state.log('dygraphUnhighlightCallback()');
4296 state.unpauseChart();
4297 state.globalSelectionSyncStop();
4299 interactionModel : {
4300 mousedown: function(event, dygraph, context) {
4301 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4302 state.log('interactionModel.mousedown()');
4304 state.dygraph_user_action = true;
4305 state.globalSelectionSyncStop();
4307 if(NETDATA.options.debug.dygraph === true)
4308 state.log('dygraphMouseDown()');
4310 // Right-click should not initiate a zoom.
4311 if(event.button && event.button === 2) return;
4313 context.initializeMouseDown(event, dygraph, context);
4315 if(event.button && event.button === 1) {
4316 if (event.altKey || event.shiftKey) {
4317 state.setMode('pan');
4318 state.globalSelectionSyncDelay();
4319 Dygraph.startPan(event, dygraph, context);
4322 state.setMode('zoom');
4323 state.globalSelectionSyncDelay();
4324 Dygraph.startZoom(event, dygraph, context);
4328 if (event.altKey || event.shiftKey) {
4329 state.setMode('zoom');
4330 state.globalSelectionSyncDelay();
4331 Dygraph.startZoom(event, dygraph, context);
4334 state.setMode('pan');
4335 state.globalSelectionSyncDelay();
4336 Dygraph.startPan(event, dygraph, context);
4340 mousemove: function(event, dygraph, context) {
4341 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4342 state.log('interactionModel.mousemove()');
4344 if(context.isPanning) {
4345 state.dygraph_user_action = true;
4346 state.globalSelectionSyncStop();
4347 state.globalSelectionSyncDelay();
4348 state.setMode('pan');
4349 context.is2DPan = false;
4350 Dygraph.movePan(event, dygraph, context);
4352 else if(context.isZooming) {
4353 state.dygraph_user_action = true;
4354 state.globalSelectionSyncStop();
4355 state.globalSelectionSyncDelay();
4356 state.setMode('zoom');
4357 Dygraph.moveZoom(event, dygraph, context);
4360 mouseup: function(event, dygraph, context) {
4361 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4362 state.log('interactionModel.mouseup()');
4364 if (context.isPanning) {
4365 state.dygraph_user_action = true;
4366 state.globalSelectionSyncDelay();
4367 Dygraph.endPan(event, dygraph, context);
4369 else if (context.isZooming) {
4370 state.dygraph_user_action = true;
4371 state.globalSelectionSyncDelay();
4372 Dygraph.endZoom(event, dygraph, context);
4375 click: function(event, dygraph, context) {
4376 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4377 state.log('interactionModel.click()');
4379 event.preventDefault();
4381 dblclick: function(event, dygraph, context) {
4382 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4383 state.log('interactionModel.dblclick()');
4384 NETDATA.resetAllCharts(state);
4386 wheel: function(event, dygraph, context) {
4387 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4388 state.log('interactionModel.wheel()');
4390 // Take the offset of a mouse event on the dygraph canvas and
4391 // convert it to a pair of percentages from the bottom left.
4392 // (Not top left, bottom is where the lower value is.)
4393 function offsetToPercentage(g, offsetX, offsetY) {
4394 // This is calculating the pixel offset of the leftmost date.
4395 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4396 var yar0 = g.yAxisRange(0);
4398 // This is calculating the pixel of the higest value. (Top pixel)
4399 var yOffset = g.toDomCoords(null, yar0[1])[1];
4401 // x y w and h are relative to the corner of the drawing area,
4402 // so that the upper corner of the drawing area is (0, 0).
4403 var x = offsetX - xOffset;
4404 var y = offsetY - yOffset;
4406 // This is computing the rightmost pixel, effectively defining the
4408 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4410 // This is computing the lowest pixel, effectively defining the height.
4411 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4413 // Percentage from the left.
4414 var xPct = w === 0 ? 0 : (x / w);
4415 // Percentage from the top.
4416 var yPct = h === 0 ? 0 : (y / h);
4418 // The (1-) part below changes it from "% distance down from the top"
4419 // to "% distance up from the bottom".
4420 return [xPct, (1-yPct)];
4423 // Adjusts [x, y] toward each other by zoomInPercentage%
4424 // Split it so the left/bottom axis gets xBias/yBias of that change and
4425 // tight/top gets (1-xBias)/(1-yBias) of that change.
4427 // If a bias is missing it splits it down the middle.
4428 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4429 xBias = xBias || 0.5;
4430 yBias = yBias || 0.5;
4432 function adjustAxis(axis, zoomInPercentage, bias) {
4433 var delta = axis[1] - axis[0];
4434 var increment = delta * zoomInPercentage;
4435 var foo = [increment * bias, increment * (1-bias)];
4437 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4440 var yAxes = g.yAxisRanges();
4442 for (var i = 0; i < yAxes.length; i++) {
4443 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4446 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4449 if(event.altKey || event.shiftKey) {
4450 state.dygraph_user_action = true;
4452 state.globalSelectionSyncStop();
4453 state.globalSelectionSyncDelay();
4455 // http://dygraphs.com/gallery/interaction-api.js
4457 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4459 normal_def = event.wheelDelta / 40;
4462 normal_def = event.deltaY * -1.2;
4464 var normal = (event.detail) ? event.detail * -1 : normal_def;
4465 var percentage = normal / 50;
4467 if (!(event.offsetX && event.offsetY)){
4468 event.offsetX = event.layerX - event.target.offsetLeft;
4469 event.offsetY = event.layerY - event.target.offsetTop;
4472 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4473 var xPct = percentages[0];
4474 var yPct = percentages[1];
4476 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4477 var after = new_x_range[0];
4478 var before = new_x_range[1];
4480 var first = state.netdata_first + state.data_update_every;
4481 var last = state.netdata_last + state.data_update_every;
4484 after -= (before - last);
4491 state.setMode('zoom');
4492 if(state.updateChartPanOrZoom(after, before) === true)
4493 dygraph.updateOptions({ dateWindow: [ after, before ] });
4495 event.preventDefault();
4498 touchstart: function(event, dygraph, context) {
4499 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500 state.log('interactionModel.touchstart()');
4502 state.dygraph_user_action = true;
4503 state.setMode('zoom');
4506 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4508 // we overwrite the touch directions at the end, to overwrite
4509 // the internal default of dygraphs
4510 context.touchDirections = { x: true, y: false };
4512 state.dygraph_last_touch_start = Date.now();
4513 state.dygraph_last_touch_move = 0;
4515 if(typeof event.touches[0].pageX === 'number')
4516 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4518 state.dygraph_last_touch_page_x = 0;
4520 touchmove: function(event, dygraph, context) {
4521 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4522 state.log('interactionModel.touchmove()');
4524 state.dygraph_user_action = true;
4525 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4527 state.dygraph_last_touch_move = Date.now();
4529 touchend: function(event, dygraph, context) {
4530 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4531 state.log('interactionModel.touchend()');
4533 state.dygraph_user_action = true;
4534 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4536 // if it didn't move, it is a selection
4537 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4538 // internal api of dygraphs
4539 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4540 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4541 if(NETDATA.dygraphSetSelection(state, t) === true)
4542 state.globalSelectionSync(t);
4545 // if it was double tap within double click time, reset the charts
4546 var now = Date.now();
4547 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4548 if(state.dygraph_last_touch_move === 0) {
4549 var dt = now - state.dygraph_last_touch_end;
4550 if(dt <= NETDATA.options.current.double_click_speed)
4551 NETDATA.resetAllCharts(state);
4555 // remember the timestamp of the last touch end
4556 state.dygraph_last_touch_end = now;
4561 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4562 state.dygraph_options.drawGrid = false;
4563 state.dygraph_options.drawAxis = false;
4564 state.dygraph_options.title = undefined;
4565 state.dygraph_options.ylabel = undefined;
4566 state.dygraph_options.yLabelWidth = 0;
4567 state.dygraph_options.labelsDivWidth = 120;
4568 state.dygraph_options.labelsDivStyles.width = '120px';
4569 state.dygraph_options.labelsSeparateLines = true;
4570 state.dygraph_options.rightGap = 0;
4571 state.dygraph_options.yRangePad = 1;
4574 if(smooth === true) {
4575 state.dygraph_smooth_eligible = true;
4577 if(NETDATA.options.current.smooth_plot === true)
4578 state.dygraph_options.plotter = smoothPlotter;
4580 else state.dygraph_smooth_eligible = false;
4582 state.dygraph_instance = new Dygraph(state.element_chart,
4583 data.result.data, state.dygraph_options);
4585 state.dygraph_force_zoom = false;
4586 state.dygraph_user_action = false;
4587 state.dygraph_last_rendered = Date.now();
4589 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4590 state.__commonMin = self.data('common-min') || null;
4591 state.__commonMax = self.data('common-max') || null;
4594 state.log('incompatible version of dygraphs detected');
4595 state.__commonMin = null;
4596 state.__commonMax = null;
4602 // ----------------------------------------------------------------------------------------------------------------
4605 NETDATA.morrisInitialize = function(callback) {
4606 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4608 // morris requires raphael
4609 if(!NETDATA.chartLibraries.raphael.initialized) {
4610 if(NETDATA.chartLibraries.raphael.enabled) {
4611 NETDATA.raphaelInitialize(function() {
4612 NETDATA.morrisInitialize(callback);
4616 NETDATA.chartLibraries.morris.enabled = false;
4617 if(typeof callback === "function")
4622 NETDATA._loadCSS(NETDATA.morris_css);
4625 url: NETDATA.morris_js,
4628 xhrFields: { withCredentials: true } // required for the cookie
4631 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4634 NETDATA.chartLibraries.morris.enabled = false;
4635 NETDATA.error(100, NETDATA.morris_js);
4637 .always(function() {
4638 if(typeof callback === "function")
4644 NETDATA.chartLibraries.morris.enabled = false;
4645 if(typeof callback === "function")
4650 NETDATA.morrisChartUpdate = function(state, data) {
4651 state.morris_instance.setData(data.result.data);
4655 NETDATA.morrisChartCreate = function(state, data) {
4657 state.morris_options = {
4658 element: state.element_chart.id,
4659 data: data.result.data,
4661 ykeys: data.dimension_names,
4662 labels: data.dimension_names,
4668 continuousLine: false,
4669 behaveLikeLine: false
4672 if(state.chart.chart_type === 'line')
4673 state.morris_instance = new Morris.Line(state.morris_options);
4675 else if(state.chart.chart_type === 'area') {
4676 state.morris_options.behaveLikeLine = true;
4677 state.morris_instance = new Morris.Area(state.morris_options);
4680 state.morris_instance = new Morris.Area(state.morris_options);
4685 // ----------------------------------------------------------------------------------------------------------------
4688 NETDATA.raphaelInitialize = function(callback) {
4689 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4691 url: NETDATA.raphael_js,
4694 xhrFields: { withCredentials: true } // required for the cookie
4697 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4700 NETDATA.chartLibraries.raphael.enabled = false;
4701 NETDATA.error(100, NETDATA.raphael_js);
4703 .always(function() {
4704 if(typeof callback === "function")
4709 NETDATA.chartLibraries.raphael.enabled = false;
4710 if(typeof callback === "function")
4715 NETDATA.raphaelChartUpdate = function(state, data) {
4716 $(state.element_chart).raphael(data.result, {
4717 width: state.chartWidth(),
4718 height: state.chartHeight()
4724 NETDATA.raphaelChartCreate = function(state, data) {
4725 $(state.element_chart).raphael(data.result, {
4726 width: state.chartWidth(),
4727 height: state.chartHeight()
4733 // ----------------------------------------------------------------------------------------------------------------
4736 NETDATA.c3Initialize = function(callback) {
4737 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4740 if(!NETDATA.chartLibraries.d3.initialized) {
4741 if(NETDATA.chartLibraries.d3.enabled) {
4742 NETDATA.d3Initialize(function() {
4743 NETDATA.c3Initialize(callback);
4747 NETDATA.chartLibraries.c3.enabled = false;
4748 if(typeof callback === "function")
4753 NETDATA._loadCSS(NETDATA.c3_css);
4759 xhrFields: { withCredentials: true } // required for the cookie
4762 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4765 NETDATA.chartLibraries.c3.enabled = false;
4766 NETDATA.error(100, NETDATA.c3_js);
4768 .always(function() {
4769 if(typeof callback === "function")
4775 NETDATA.chartLibraries.c3.enabled = false;
4776 if(typeof callback === "function")
4781 NETDATA.c3ChartUpdate = function(state, data) {
4782 state.c3_instance.destroy();
4783 return NETDATA.c3ChartCreate(state, data);
4785 //state.c3_instance.load({
4786 // rows: data.result,
4793 NETDATA.c3ChartCreate = function(state, data) {
4795 state.element_chart.id = 'c3-' + state.uuid;
4796 // console.log('id = ' + state.element_chart.id);
4798 state.c3_instance = c3.generate({
4799 bindto: '#' + state.element_chart.id,
4801 width: state.chartWidth(),
4802 height: state.chartHeight()
4805 pattern: state.chartColors()
4810 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4816 format: function(x) {
4817 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4844 // console.log(state.c3_instance);
4849 // ----------------------------------------------------------------------------------------------------------------
4852 NETDATA.d3Initialize = function(callback) {
4853 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4858 xhrFields: { withCredentials: true } // required for the cookie
4861 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4864 NETDATA.chartLibraries.d3.enabled = false;
4865 NETDATA.error(100, NETDATA.d3_js);
4867 .always(function() {
4868 if(typeof callback === "function")
4873 NETDATA.chartLibraries.d3.enabled = false;
4874 if(typeof callback === "function")
4879 NETDATA.d3ChartUpdate = function(state, data) {
4883 NETDATA.d3ChartCreate = function(state, data) {
4887 // ----------------------------------------------------------------------------------------------------------------
4890 NETDATA.googleInitialize = function(callback) {
4891 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4893 url: NETDATA.google_js,
4896 xhrFields: { withCredentials: true } // required for the cookie
4899 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4900 google.load('visualization', '1.1', {
4901 'packages': ['corechart', 'controls'],
4902 'callback': callback
4906 NETDATA.chartLibraries.google.enabled = false;
4907 NETDATA.error(100, NETDATA.google_js);
4908 if(typeof callback === "function")
4913 NETDATA.chartLibraries.google.enabled = false;
4914 if(typeof callback === "function")
4919 NETDATA.googleChartUpdate = function(state, data) {
4920 var datatable = new google.visualization.DataTable(data.result);
4921 state.google_instance.draw(datatable, state.google_options);
4925 NETDATA.googleChartCreate = function(state, data) {
4926 var datatable = new google.visualization.DataTable(data.result);
4928 state.google_options = {
4929 colors: state.chartColors(),
4931 // do not set width, height - the chart resizes itself
4932 //width: state.chartWidth(),
4933 //height: state.chartHeight(),
4938 // title: "Time of Day",
4939 // format:'HH:mm:ss',
4940 viewWindowMode: 'maximized',
4952 viewWindowMode: 'pretty',
4967 focusTarget: 'category',
4974 titlePosition: 'out',
4985 curveType: 'function',
4990 switch(state.chart.chart_type) {
4992 state.google_options.vAxis.viewWindowMode = 'maximized';
4993 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4994 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4998 state.google_options.isStacked = true;
4999 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5000 state.google_options.vAxis.viewWindowMode = 'maximized';
5001 state.google_options.vAxis.minValue = null;
5002 state.google_options.vAxis.maxValue = null;
5003 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5008 state.google_options.lineWidth = 2;
5009 state.google_instance = new google.visualization.LineChart(state.element_chart);
5013 state.google_instance.draw(datatable, state.google_options);
5017 // ----------------------------------------------------------------------------------------------------------------
5019 NETDATA.percentFromValueMinMax = function(value, min, max) {
5020 if(typeof value !== 'number') value = 0;
5021 if(typeof min !== 'number') min = 0;
5022 if(typeof max !== 'number') max = 0;
5026 if(max < value) max = value;
5028 pcent = Math.round(value * 100 / max);
5029 if(pcent === 0 && value > 0) pcent = 1;
5033 if(min > value) min = value;
5035 pcent = Math.round(-value * 100 / min);
5036 if(pcent === 0 && value < 0) pcent = -1;
5043 // ----------------------------------------------------------------------------------------------------------------
5046 NETDATA.easypiechartInitialize = function(callback) {
5047 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5049 url: NETDATA.easypiechart_js,
5052 xhrFields: { withCredentials: true } // required for the cookie
5055 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5058 NETDATA.chartLibraries.easypiechart.enabled = false;
5059 NETDATA.error(100, NETDATA.easypiechart_js);
5061 .always(function() {
5062 if(typeof callback === "function")
5067 NETDATA.chartLibraries.easypiechart.enabled = false;
5068 if(typeof callback === "function")
5073 NETDATA.easypiechartClearSelection = function(state) {
5074 if(typeof state.easyPieChartEvent !== 'undefined') {
5075 if(state.easyPieChartEvent.timer !== null)
5076 clearTimeout(state.easyPieChartEvent.timer);
5078 state.easyPieChartEvent.timer = null;
5081 if(state.isAutoRefreshable() === true && state.data !== null) {
5082 NETDATA.easypiechartChartUpdate(state, state.data);
5085 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5086 state.easyPieChart_instance.update(0);
5088 state.easyPieChart_instance.enableAnimation();
5093 NETDATA.easypiechartSetSelection = function(state, t) {
5094 if(state.timeIsVisible(t) !== true)
5095 return NETDATA.easypiechartClearSelection(state);
5097 var slot = state.calculateRowForTime(t);
5098 if(slot < 0 || slot >= state.data.result.length)
5099 return NETDATA.easypiechartClearSelection(state);
5101 if(typeof state.easyPieChartEvent === 'undefined') {
5102 state.easyPieChartEvent = {
5109 var value = state.data.result[state.data.result.length - 1 - slot];
5110 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5111 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5112 var pcent = NETDATA.percentFromValueMinMax(value, min, max);
5114 state.easyPieChartEvent.value = value;
5115 state.easyPieChartEvent.pcent = pcent;
5116 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5118 if(state.easyPieChartEvent.timer === null) {
5119 state.easyPieChart_instance.disableAnimation();
5121 state.easyPieChartEvent.timer = setTimeout(function() {
5122 state.easyPieChartEvent.timer = null;
5123 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5124 }, NETDATA.options.current.charts_selection_animation_delay);
5130 NETDATA.easypiechartChartUpdate = function(state, data) {
5131 var value, min, max, pcent;
5133 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5138 value = data.result[0];
5139 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5140 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5141 pcent = NETDATA.percentFromValueMinMax(value, min, max);
5144 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5145 state.easyPieChart_instance.update(pcent);
5149 NETDATA.easypiechartChartCreate = function(state, data) {
5150 var self = $(state.element);
5151 var chart = $(state.element_chart);
5153 var value = data.result[0];
5154 var min = self.data('easypiechart-min-value') || null;
5155 var max = self.data('easypiechart-max-value') || null;
5156 var adjust = self.data('easypiechart-adjust') || null;
5159 min = NETDATA.commonMin.get(state);
5160 state.easyPieChartMin = null;
5163 state.easyPieChartMin = min;
5166 max = NETDATA.commonMax.get(state);
5167 state.easyPieChartMax = null;
5170 state.easyPieChartMax = max;
5172 var pcent = NETDATA.percentFromValueMinMax(value, min, max);
5174 chart.data('data-percent', pcent);
5178 case 'width': size = state.chartHeight(); break;
5179 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5180 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5182 default: size = state.chartWidth(); break;
5184 state.element.style.width = size + 'px';
5185 state.element.style.height = size + 'px';
5187 var stroke = Math.floor(size / 22);
5188 if(stroke < 3) stroke = 2;
5190 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5191 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5192 state.easyPieChartLabel = document.createElement('span');
5193 state.easyPieChartLabel.className = 'easyPieChartLabel';
5194 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5195 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5196 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5197 state.element_chart.appendChild(state.easyPieChartLabel);
5199 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5200 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5201 state.easyPieChartTitle = document.createElement('span');
5202 state.easyPieChartTitle.className = 'easyPieChartTitle';
5203 state.easyPieChartTitle.innerHTML = state.title;
5204 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5205 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5206 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5207 state.element_chart.appendChild(state.easyPieChartTitle);
5209 var unitfontsize = Math.round(titlefontsize * 0.9);
5210 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5211 state.easyPieChartUnits = document.createElement('span');
5212 state.easyPieChartUnits.className = 'easyPieChartUnits';
5213 state.easyPieChartUnits.innerHTML = state.units;
5214 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5215 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5216 state.element_chart.appendChild(state.easyPieChartUnits);
5218 var barColor = self.data('easypiechart-barcolor');
5219 if(typeof barColor === 'undefined' || barColor === null)
5220 barColor = state.chartColors()[0];
5222 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5223 var tmp = eval(barColor);
5224 if(typeof tmp === 'function')
5228 chart.easyPieChart({
5230 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5231 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5232 scaleLength: self.data('easypiechart-scalelength') || 5,
5233 lineCap: self.data('easypiechart-linecap') || 'round',
5234 lineWidth: self.data('easypiechart-linewidth') || stroke,
5235 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5236 size: self.data('easypiechart-size') || size,
5237 rotate: self.data('easypiechart-rotate') || 0,
5238 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5239 easing: self.data('easypiechart-easing') || undefined
5242 // when we just re-create the chart
5243 // do not animate the first update
5245 if(typeof state.easyPieChart_instance !== 'undefined')
5248 state.easyPieChart_instance = chart.data('easyPieChart');
5249 if(animate === false) state.easyPieChart_instance.disableAnimation();
5250 state.easyPieChart_instance.update(pcent);
5251 if(animate === false) state.easyPieChart_instance.enableAnimation();
5255 // ----------------------------------------------------------------------------------------------------------------
5258 NETDATA.gaugeInitialize = function(callback) {
5259 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5261 url: NETDATA.gauge_js,
5264 xhrFields: { withCredentials: true } // required for the cookie
5267 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5270 NETDATA.chartLibraries.gauge.enabled = false;
5271 NETDATA.error(100, NETDATA.gauge_js);
5273 .always(function() {
5274 if(typeof callback === "function")
5279 NETDATA.chartLibraries.gauge.enabled = false;
5280 if(typeof callback === "function")
5285 NETDATA.gaugeAnimation = function(state, status) {
5288 if(typeof status === 'boolean' && status === false)
5290 else if(typeof status === 'number')
5293 // console.log('gauge speed ' + speed);
5294 state.gauge_instance.animationSpeed = speed;
5295 state.___gaugeOld__.speed = speed;
5298 NETDATA.gaugeSet = function(state, value, min, max) {
5299 if(typeof value !== 'number') value = 0;
5300 if(typeof min !== 'number') min = 0;
5301 if(typeof max !== 'number') max = 0;
5302 if(value > max) max = value;
5303 if(value < min) min = value;
5312 // gauge.js has an issue if the needle
5313 // is smaller than min or larger than max
5314 // when we set the new values
5315 // the needle will go crazy
5317 // to prevent it, we always feed it
5318 // with a percentage, so that the needle
5319 // is always between min and max
5320 var pcent = (value - min) * 100 / (max - min);
5322 // these should never happen
5323 if(pcent < 0) pcent = 0;
5324 if(pcent > 100) pcent = 100;
5326 state.gauge_instance.set(pcent);
5327 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5329 state.___gaugeOld__.value = value;
5330 state.___gaugeOld__.min = min;
5331 state.___gaugeOld__.max = max;
5334 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5335 if(state.___gaugeOld__.valueLabel !== value) {
5336 state.___gaugeOld__.valueLabel = value;
5337 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5339 if(state.___gaugeOld__.minLabel !== min) {
5340 state.___gaugeOld__.minLabel = min;
5341 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5343 if(state.___gaugeOld__.maxLabel !== max) {
5344 state.___gaugeOld__.maxLabel = max;
5345 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5349 NETDATA.gaugeClearSelection = function(state) {
5350 if(typeof state.gaugeEvent !== 'undefined') {
5351 if(state.gaugeEvent.timer !== null)
5352 clearTimeout(state.gaugeEvent.timer);
5354 state.gaugeEvent.timer = null;
5357 if(state.isAutoRefreshable() === true && state.data !== null) {
5358 NETDATA.gaugeChartUpdate(state, state.data);
5361 NETDATA.gaugeAnimation(state, false);
5362 NETDATA.gaugeSet(state, null, null, null);
5363 NETDATA.gaugeSetLabels(state, null, null, null);
5366 NETDATA.gaugeAnimation(state, true);
5370 NETDATA.gaugeSetSelection = function(state, t) {
5371 if(state.timeIsVisible(t) !== true)
5372 return NETDATA.gaugeClearSelection(state);
5374 var slot = state.calculateRowForTime(t);
5375 if(slot < 0 || slot >= state.data.result.length)
5376 return NETDATA.gaugeClearSelection(state);
5378 if(typeof state.gaugeEvent === 'undefined') {
5379 state.gaugeEvent = {
5387 var value = state.data.result[state.data.result.length - 1 - slot];
5388 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5389 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5391 state.gaugeEvent.value = value;
5392 state.gaugeEvent.min = min;
5393 state.gaugeEvent.max = max;
5394 NETDATA.gaugeSetLabels(state, value, min, max);
5396 if(state.gaugeEvent.timer === null) {
5397 NETDATA.gaugeAnimation(state, false);
5399 state.gaugeEvent.timer = setTimeout(function() {
5400 state.gaugeEvent.timer = null;
5401 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5402 }, NETDATA.options.current.charts_selection_animation_delay);
5408 NETDATA.gaugeChartUpdate = function(state, data) {
5409 var value, min, max;
5411 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5415 NETDATA.gaugeSetLabels(state, null, null, null);
5418 value = data.result[0];
5419 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5420 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5421 if(value < min) min = value;
5422 if(value > max) max = value;
5423 NETDATA.gaugeSetLabels(state, value, min, max);
5426 NETDATA.gaugeSet(state, value, min, max);
5430 NETDATA.gaugeChartCreate = function(state, data) {
5431 var self = $(state.element);
5432 // var chart = $(state.element_chart);
5434 var value = data.result[0];
5435 var min = self.data('gauge-min-value') || null;
5436 var max = self.data('gauge-max-value') || null;
5437 var adjust = self.data('gauge-adjust') || null;
5438 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5439 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5440 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5441 var stopColor = self.data('gauge-stop-color') || void 0;
5442 var generateGradient = self.data('gauge-generate-gradient') || false;
5445 min = NETDATA.commonMin.get(state);
5446 state.gaugeMin = null;
5449 state.gaugeMin = min;
5452 max = NETDATA.commonMax.get(state);
5453 state.gaugeMax = null;
5456 state.gaugeMax = max;
5458 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5460 // case 'width': width = height * ratio; break;
5462 // default: height = width / ratio; break;
5464 //state.element.style.width = width.toString() + 'px';
5465 //state.element.style.height = height.toString() + 'px';
5470 lines: 12, // The number of lines to draw
5471 angle: 0.15, // The length of each line
5472 lineWidth: 0.44, // 0.44 The line thickness
5474 length: 0.8, // 0.9 The radius of the inner circle
5475 strokeWidth: 0.035, // The rotation offset
5476 color: pointerColor // Fill color
5478 colorStart: startColor, // Colors
5479 colorStop: stopColor, // just experiment with them
5480 strokeColor: strokeColor, // to see which ones work best for you
5482 generateGradient: (generateGradient === true)?true:false,
5486 if (generateGradient.constructor === Array) {
5488 // data-gauge-generate-gradient="[0, 50, 100]"
5489 // data-gauge-gradient-percent-color-0="#FFFFFF"
5490 // data-gauge-gradient-percent-color-50="#999900"
5491 // data-gauge-gradient-percent-color-100="#000000"
5493 options.percentColors = new Array();
5494 var len = generateGradient.length;
5496 var pcent = generateGradient[len];
5497 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5498 if(color !== false) {
5499 var a = new Array();
5502 options.percentColors.unshift(a);
5505 if(options.percentColors.length === 0)
5506 delete options.percentColors;
5508 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5509 options.percentColors = [
5510 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5511 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5512 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5513 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5514 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5515 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5516 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5517 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5518 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5519 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5520 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5523 state.gauge_canvas = document.createElement('canvas');
5524 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5525 state.gauge_canvas.className = 'gaugeChart';
5526 state.gauge_canvas.width = width;
5527 state.gauge_canvas.height = height;
5528 state.element_chart.appendChild(state.gauge_canvas);
5530 var valuefontsize = Math.floor(height / 6);
5531 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5532 state.gaugeChartLabel = document.createElement('span');
5533 state.gaugeChartLabel.className = 'gaugeChartLabel';
5534 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5535 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5536 state.element_chart.appendChild(state.gaugeChartLabel);
5538 var titlefontsize = Math.round(valuefontsize / 2);
5540 state.gaugeChartTitle = document.createElement('span');
5541 state.gaugeChartTitle.className = 'gaugeChartTitle';
5542 state.gaugeChartTitle.innerHTML = state.title;
5543 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5544 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5545 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5546 state.element_chart.appendChild(state.gaugeChartTitle);
5548 var unitfontsize = Math.round(titlefontsize * 0.9);
5549 state.gaugeChartUnits = document.createElement('span');
5550 state.gaugeChartUnits.className = 'gaugeChartUnits';
5551 state.gaugeChartUnits.innerHTML = state.units;
5552 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5553 state.element_chart.appendChild(state.gaugeChartUnits);
5555 state.gaugeChartMin = document.createElement('span');
5556 state.gaugeChartMin.className = 'gaugeChartMin';
5557 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5558 state.element_chart.appendChild(state.gaugeChartMin);
5560 state.gaugeChartMax = document.createElement('span');
5561 state.gaugeChartMax.className = 'gaugeChartMax';
5562 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5563 state.element_chart.appendChild(state.gaugeChartMax);
5565 // when we just re-create the chart
5566 // do not animate the first update
5568 if(typeof state.gauge_instance !== 'undefined')
5571 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5573 state.___gaugeOld__ = {
5582 // we will always feed a percentage
5583 state.gauge_instance.minValue = 0;
5584 state.gauge_instance.maxValue = 100;
5586 NETDATA.gaugeAnimation(state, animate);
5587 NETDATA.gaugeSet(state, value, min, max);
5588 NETDATA.gaugeSetLabels(state, value, min, max);
5589 NETDATA.gaugeAnimation(state, true);
5593 // ----------------------------------------------------------------------------------------------------------------
5594 // Charts Libraries Registration
5596 NETDATA.chartLibraries = {
5598 initialize: NETDATA.dygraphInitialize,
5599 create: NETDATA.dygraphChartCreate,
5600 update: NETDATA.dygraphChartUpdate,
5601 resize: function(state) {
5602 if(typeof state.dygraph_instance.resize === 'function')
5603 state.dygraph_instance.resize();
5605 setSelection: NETDATA.dygraphSetSelection,
5606 clearSelection: NETDATA.dygraphClearSelection,
5607 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5610 format: function(state) { return 'json'; },
5611 options: function(state) { return 'ms|flip'; },
5612 legend: function(state) {
5613 if(this.isSparkline(state) === false)
5614 return 'right-side';
5618 autoresize: function(state) { return true; },
5619 max_updates_to_recreate: function(state) { return 5000; },
5620 track_colors: function(state) { return true; },
5621 pixels_per_point: function(state) {
5622 if(this.isSparkline(state) === false)
5628 isSparkline: function(state) {
5629 if(typeof state.dygraph_sparkline === 'undefined') {
5630 var t = $(state.element).data('dygraph-theme');
5631 if(t === 'sparkline')
5632 state.dygraph_sparkline = true;
5634 state.dygraph_sparkline = false;
5636 return state.dygraph_sparkline;
5640 initialize: NETDATA.sparklineInitialize,
5641 create: NETDATA.sparklineChartCreate,
5642 update: NETDATA.sparklineChartUpdate,
5644 setSelection: undefined, // function(state, t) { return true; },
5645 clearSelection: undefined, // function(state) { return true; },
5646 toolboxPanAndZoom: null,
5649 format: function(state) { return 'array'; },
5650 options: function(state) { return 'flip|abs'; },
5651 legend: function(state) { return null; },
5652 autoresize: function(state) { return false; },
5653 max_updates_to_recreate: function(state) { return 5000; },
5654 track_colors: function(state) { return false; },
5655 pixels_per_point: function(state) { return 3; }
5658 initialize: NETDATA.peityInitialize,
5659 create: NETDATA.peityChartCreate,
5660 update: NETDATA.peityChartUpdate,
5662 setSelection: undefined, // function(state, t) { return true; },
5663 clearSelection: undefined, // function(state) { return true; },
5664 toolboxPanAndZoom: null,
5667 format: function(state) { return 'ssvcomma'; },
5668 options: function(state) { return 'null2zero|flip|abs'; },
5669 legend: function(state) { return null; },
5670 autoresize: function(state) { return false; },
5671 max_updates_to_recreate: function(state) { return 5000; },
5672 track_colors: function(state) { return false; },
5673 pixels_per_point: function(state) { return 3; }
5676 initialize: NETDATA.morrisInitialize,
5677 create: NETDATA.morrisChartCreate,
5678 update: NETDATA.morrisChartUpdate,
5680 setSelection: undefined, // function(state, t) { return true; },
5681 clearSelection: undefined, // function(state) { return true; },
5682 toolboxPanAndZoom: null,
5685 format: function(state) { return 'json'; },
5686 options: function(state) { return 'objectrows|ms'; },
5687 legend: function(state) { return null; },
5688 autoresize: function(state) { return false; },
5689 max_updates_to_recreate: function(state) { return 50; },
5690 track_colors: function(state) { return false; },
5691 pixels_per_point: function(state) { return 15; }
5694 initialize: NETDATA.googleInitialize,
5695 create: NETDATA.googleChartCreate,
5696 update: NETDATA.googleChartUpdate,
5698 setSelection: undefined, //function(state, t) { return true; },
5699 clearSelection: undefined, //function(state) { return true; },
5700 toolboxPanAndZoom: null,
5703 format: function(state) { return 'datatable'; },
5704 options: function(state) { return ''; },
5705 legend: function(state) { return null; },
5706 autoresize: function(state) { return false; },
5707 max_updates_to_recreate: function(state) { return 300; },
5708 track_colors: function(state) { return false; },
5709 pixels_per_point: function(state) { return 4; }
5712 initialize: NETDATA.raphaelInitialize,
5713 create: NETDATA.raphaelChartCreate,
5714 update: NETDATA.raphaelChartUpdate,
5716 setSelection: undefined, // function(state, t) { return true; },
5717 clearSelection: undefined, // function(state) { return true; },
5718 toolboxPanAndZoom: null,
5721 format: function(state) { return 'json'; },
5722 options: function(state) { return ''; },
5723 legend: function(state) { return null; },
5724 autoresize: function(state) { return false; },
5725 max_updates_to_recreate: function(state) { return 5000; },
5726 track_colors: function(state) { return false; },
5727 pixels_per_point: function(state) { return 3; }
5730 initialize: NETDATA.c3Initialize,
5731 create: NETDATA.c3ChartCreate,
5732 update: NETDATA.c3ChartUpdate,
5734 setSelection: undefined, // function(state, t) { return true; },
5735 clearSelection: undefined, // function(state) { return true; },
5736 toolboxPanAndZoom: null,
5739 format: function(state) { return 'csvjsonarray'; },
5740 options: function(state) { return 'milliseconds'; },
5741 legend: function(state) { return null; },
5742 autoresize: function(state) { return false; },
5743 max_updates_to_recreate: function(state) { return 5000; },
5744 track_colors: function(state) { return false; },
5745 pixels_per_point: function(state) { return 15; }
5748 initialize: NETDATA.d3Initialize,
5749 create: NETDATA.d3ChartCreate,
5750 update: NETDATA.d3ChartUpdate,
5752 setSelection: undefined, // function(state, t) { return true; },
5753 clearSelection: undefined, // function(state) { return true; },
5754 toolboxPanAndZoom: null,
5757 format: function(state) { return 'json'; },
5758 options: function(state) { return ''; },
5759 legend: function(state) { return null; },
5760 autoresize: function(state) { return false; },
5761 max_updates_to_recreate: function(state) { return 5000; },
5762 track_colors: function(state) { return false; },
5763 pixels_per_point: function(state) { return 3; }
5766 initialize: NETDATA.easypiechartInitialize,
5767 create: NETDATA.easypiechartChartCreate,
5768 update: NETDATA.easypiechartChartUpdate,
5770 setSelection: NETDATA.easypiechartSetSelection,
5771 clearSelection: NETDATA.easypiechartClearSelection,
5772 toolboxPanAndZoom: null,
5775 format: function(state) { return 'array'; },
5776 options: function(state) { return 'absolute'; },
5777 legend: function(state) { return null; },
5778 autoresize: function(state) { return false; },
5779 max_updates_to_recreate: function(state) { return 5000; },
5780 track_colors: function(state) { return true; },
5781 pixels_per_point: function(state) { return 3; },
5785 initialize: NETDATA.gaugeInitialize,
5786 create: NETDATA.gaugeChartCreate,
5787 update: NETDATA.gaugeChartUpdate,
5789 setSelection: NETDATA.gaugeSetSelection,
5790 clearSelection: NETDATA.gaugeClearSelection,
5791 toolboxPanAndZoom: null,
5794 format: function(state) { return 'array'; },
5795 options: function(state) { return 'absolute'; },
5796 legend: function(state) { return null; },
5797 autoresize: function(state) { return false; },
5798 max_updates_to_recreate: function(state) { return 5000; },
5799 track_colors: function(state) { return true; },
5800 pixels_per_point: function(state) { return 3; },
5805 NETDATA.registerChartLibrary = function(library, url) {
5806 if(NETDATA.options.debug.libraries === true)
5807 console.log("registering chart library: " + library);
5809 NETDATA.chartLibraries[library].url = url;
5810 NETDATA.chartLibraries[library].initialized = true;
5811 NETDATA.chartLibraries[library].enabled = true;
5814 // ----------------------------------------------------------------------------------------------------------------
5815 // Load required JS libraries and CSS
5817 NETDATA.requiredJs = [
5819 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5821 isAlreadyLoaded: function() {
5822 // check if bootstrap is loaded
5823 if(typeof $().emulateTransitionEnd == 'function')
5826 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5834 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5835 isAlreadyLoaded: function() { return false; }
5839 NETDATA.requiredCSS = [
5841 url: NETDATA.themes.current.bootstrap_css,
5842 isAlreadyLoaded: function() {
5843 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5850 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5851 isAlreadyLoaded: function() { return false; }
5854 url: NETDATA.themes.current.dashboard_css,
5855 isAlreadyLoaded: function() { return false; }
5859 NETDATA.loadedRequiredJs = 0;
5860 NETDATA.loadRequiredJs = function(index, callback) {
5861 if(index >= NETDATA.requiredJs.length) {
5862 if(typeof callback === 'function')
5867 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5868 NETDATA.loadedRequiredJs++;
5869 NETDATA.loadRequiredJs(++index, callback);
5873 if(NETDATA.options.debug.main_loop === true)
5874 console.log('loading ' + NETDATA.requiredJs[index].url);
5877 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5881 url: NETDATA.requiredJs[index].url,
5884 xhrFields: { withCredentials: true } // required for the cookie
5887 if(NETDATA.options.debug.main_loop === true)
5888 console.log('loaded ' + NETDATA.requiredJs[index].url);
5891 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5893 .always(function() {
5894 NETDATA.loadedRequiredJs++;
5897 NETDATA.loadRequiredJs(++index, callback);
5901 NETDATA.loadRequiredJs(++index, callback);
5904 NETDATA.loadRequiredCSS = function(index) {
5905 if(index >= NETDATA.requiredCSS.length)
5908 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5909 NETDATA.loadRequiredCSS(++index);
5913 if(NETDATA.options.debug.main_loop === true)
5914 console.log('loading ' + NETDATA.requiredCSS[index].url);
5916 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5917 NETDATA.loadRequiredCSS(++index);
5921 // ----------------------------------------------------------------------------------------------------------------
5922 // Registry of netdata hosts
5925 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5926 chart_div_offset: 100, // give that space above the chart when scrolling to it
5927 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5928 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5930 ms_penalty: 0, // the time penalty of the next alarm
5931 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5932 // if alarms are shown faster than: one per 500ms
5934 notifications: false, // when true, the browser supports notifications (may not be granted though)
5935 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5936 first_notification_id: 0, // the id of the first alarm_log entry for this session
5937 // this is used to prevent CLEAR notifications for past events
5938 // notifications_shown: new Array(),
5940 server: null, // the server to connect to for fetching alarms
5941 current: null, // the list of raised alarms - updated in the background
5942 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5944 notify: function(entry) {
5945 // console.log('alarm ' + entry.unique_id);
5947 if(entry.updated === true) {
5948 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5952 var value = entry.value;
5953 if(NETDATA.alarms.current !== null) {
5954 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5955 if(typeof t !== 'undefined' && entry.status == t.status)
5959 var name = entry.name.replace(/_/g, ' ');
5960 var status = entry.status.toLowerCase();
5961 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5962 var tag = entry.alarm_id;
5963 var icon = 'images/seo-performance-128.png';
5964 var interaction = false;
5968 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5970 switch(entry.status) {
5978 case 'UNINITIALIZED':
5982 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5983 // console.log('alarm ' + entry.unique_id + ' is not current');
5986 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5987 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5990 title = name + ' back to normal';
5991 icon = 'images/check-mark-2-128-green.png'
5992 interaction = false;
5996 if(entry.old_status === 'CRITICAL')
5997 status = 'demoted to ' + entry.status.toLowerCase();
5999 icon = 'images/alert-128-orange.png';
6000 interaction = false;
6004 if(entry.old_status === 'WARNING')
6005 status = 'escalated to ' + entry.status.toLowerCase();
6007 icon = 'images/alert-128-red.png'
6012 console.log('invalid alarm status ' + entry.status);
6017 // cleanup old notifications with the same alarm_id as this one
6018 // FIXME: it does not seem to work on any web browser!
6019 var len = NETDATA.alarms.notifications_shown.length;
6021 var n = NETDATA.alarms.notifications_shown[len];
6022 if(n.data.alarm_id === entry.alarm_id) {
6023 console.log('removing old alarm ' + n.data.unique_id);
6025 // close the notification
6028 // remove it from the array
6029 NETDATA.alarms.notifications_shown.splice(len, 1);
6030 len = NETDATA.alarms.notifications_shown.length;
6037 setTimeout(function() {
6038 // show this notification
6039 // console.log('new notification: ' + title);
6040 var n = new Notification(title, {
6041 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6043 requireInteraction: interaction,
6044 icon: NETDATA.serverDefault + icon,
6048 n.onclick = function(event) {
6049 event.preventDefault();
6050 NETDATA.alarms.onclick(event.target.data);
6054 // NETDATA.alarms.notifications_shown.push(n);
6055 // console.log(entry);
6056 }, NETDATA.alarms.ms_penalty);
6058 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6062 scrollToChart: function(chart_id) {
6063 if(typeof chart_id === 'string') {
6064 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6065 if(typeof offset !== 'undefined') {
6066 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6073 scrollToAlarm: function(alarm) {
6074 if(typeof alarm === 'object') {
6075 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6077 if(ret === true && NETDATA.options.page_is_visible === false)
6079 // 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.');
6084 notifyAll: function() {
6085 // console.log('FETCHING ALARM LOG');
6086 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6087 // console.log('ALARM LOG FETCHED');
6089 if(data === null || typeof data !== 'object') {
6090 console.log('invalid alarms log response');
6094 if(data.length === 0) {
6095 console.log('received empty alarm log');
6099 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6101 data.sort(function(a, b) {
6102 if(a.unique_id > b.unique_id) return -1;
6103 if(a.unique_id < b.unique_id) return 1;
6107 NETDATA.alarms.ms_penalty = 0;
6109 var len = data.length;
6111 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6112 NETDATA.alarms.notify(data[len]);
6115 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6118 NETDATA.alarms.last_notification_id = data[0].unique_id;
6119 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6120 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6124 check_notifications: function() {
6125 // returns true if we should fire 1+ notifications
6127 if(NETDATA.alarms.notifications !== true) {
6128 // console.log('notifications not available');
6132 if(Notification.permission !== 'granted') {
6133 // console.log('notifications not granted');
6137 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6138 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6140 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6141 // console.log('new alarms detected');
6144 //else console.log('no new alarms');
6146 // else console.log('cannot process alarms');
6151 get: function(what, callback) {
6153 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6157 'Cache-Control': 'no-cache, no-store',
6158 'Pragma': 'no-cache'
6160 xhrFields: { withCredentials: true } // required for the cookie
6162 .done(function(data) {
6163 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6164 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6166 if(typeof callback === 'function')
6170 NETDATA.error(415, NETDATA.alarms.server);
6172 if(typeof callback === 'function')
6177 update_forever: function() {
6178 NETDATA.alarms.get('active', function(data) {
6180 NETDATA.alarms.current = data;
6182 if(NETDATA.alarms.check_notifications() === true) {
6183 NETDATA.alarms.notifyAll();
6186 if (typeof NETDATA.alarms.callback === 'function') {
6187 NETDATA.alarms.callback(data);
6190 // Health monitoring is disabled on this netdata
6191 if(data.status === false) return;
6194 setTimeout(NETDATA.alarms.update_forever, 10000);
6198 get_log: function(last_id, callback) {
6199 // console.log('fetching all log after ' + last_id.toString());
6201 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6205 'Cache-Control': 'no-cache, no-store',
6206 'Pragma': 'no-cache'
6208 xhrFields: { withCredentials: true } // required for the cookie
6210 .done(function(data) {
6211 if(typeof callback === 'function')
6215 NETDATA.error(416, NETDATA.alarms.server);
6217 if(typeof callback === 'function')
6223 var host = NETDATA.serverDefault;
6224 while(host.slice(-1) === '/')
6225 host = host.substring(0, host.length - 1);
6226 NETDATA.alarms.server = host;
6228 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6230 if(NETDATA.alarms.onclick === null)
6231 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6233 if(netdataShowAlarms === true) {
6234 NETDATA.alarms.update_forever();
6236 if('Notification' in window) {
6237 // console.log('notifications available');
6238 NETDATA.alarms.notifications = true;
6240 if(Notification.permission === 'default')
6241 Notification.requestPermission();
6247 // ----------------------------------------------------------------------------------------------------------------
6248 // Registry of netdata hosts
6250 NETDATA.registry = {
6251 server: null, // the netdata registry server
6252 person_guid: null, // the unique ID of this browser / user
6253 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6254 hostname: null, // the hostname of the netdata server that served dashboard.js
6255 machines: null, // the user's other URLs
6256 machines_array: null, // the user's other URLs in an array
6259 parsePersonUrls: function(person_urls) {
6260 // console.log(person_urls);
6261 NETDATA.registry.person_urls = person_urls;
6264 NETDATA.registry.machines = {};
6265 NETDATA.registry.machines_array = new Array();
6267 var now = Date.now();
6268 var apu = person_urls;
6271 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6272 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6278 accesses: apu[i][3],
6280 alternate_urls: new Array()
6282 obj.alternate_urls.push(apu[i][1]);
6284 NETDATA.registry.machines[apu[i][0]] = obj;
6285 NETDATA.registry.machines_array.push(obj);
6288 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6290 var pu = NETDATA.registry.machines[apu[i][0]];
6291 if(pu.last_t < apu[i][2]) {
6293 pu.last_t = apu[i][2];
6294 pu.name = apu[i][4];
6296 pu.accesses += apu[i][3];
6297 pu.alternate_urls.push(apu[i][1]);
6302 if(typeof netdataRegistryCallback === 'function')
6303 netdataRegistryCallback(NETDATA.registry.machines_array);
6307 if(netdataRegistry !== true) return;
6309 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6311 NETDATA.registry.server = data.registry;
6312 NETDATA.registry.machine_guid = data.machine_guid;
6313 NETDATA.registry.hostname = data.hostname;
6315 NETDATA.registry.access(2, function (person_urls) {
6316 NETDATA.registry.parsePersonUrls(person_urls);
6323 hello: function(host, callback) {
6324 while(host.slice(-1) === '/')
6325 host = host.substring(0, host.length - 1);
6327 // send HELLO to a netdata server:
6328 // 1. verifies the server is reachable
6329 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6331 url: host + '/api/v1/registry?action=hello',
6335 'Cache-Control': 'no-cache, no-store',
6336 'Pragma': 'no-cache'
6338 xhrFields: { withCredentials: true } // required for the cookie
6340 .done(function(data) {
6341 if(typeof data.status !== 'string' || data.status !== 'ok') {
6342 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6346 if(typeof callback === 'function')
6350 NETDATA.error(407, host);
6352 if(typeof callback === 'function')
6357 access: function(max_redirects, callback) {
6358 // send ACCESS to a netdata registry:
6359 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6360 // 2. it responds with a list of netdata servers we know
6361 // the registry identifies us using a cookie it sets the first time we access it
6362 // the registry may respond with a redirect URL to send us to another registry
6364 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),
6368 'Cache-Control': 'no-cache, no-store',
6369 'Pragma': 'no-cache'
6371 xhrFields: { withCredentials: true } // required for the cookie
6373 .done(function(data) {
6374 var redirect = null;
6375 if(typeof data.registry === 'string')
6376 redirect = data.registry;
6378 if(typeof data.status !== 'string' || data.status !== 'ok') {
6379 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6384 if(redirect !== null && max_redirects > 0) {
6385 NETDATA.registry.server = redirect;
6386 NETDATA.registry.access(max_redirects - 1, callback);
6389 if(typeof callback === 'function')
6394 if(typeof data.person_guid === 'string')
6395 NETDATA.registry.person_guid = data.person_guid;
6397 if(typeof callback === 'function')
6398 callback(data.urls);
6402 NETDATA.error(410, NETDATA.registry.server);
6404 if(typeof callback === 'function')
6409 delete: function(delete_url, callback) {
6410 // send DELETE to a netdata registry:
6412 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),
6416 'Cache-Control': 'no-cache, no-store',
6417 'Pragma': 'no-cache'
6419 xhrFields: { withCredentials: true } // required for the cookie
6421 .done(function(data) {
6422 if(typeof data.status !== 'string' || data.status !== 'ok') {
6423 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6427 if(typeof callback === 'function')
6431 NETDATA.error(412, NETDATA.registry.server);
6433 if(typeof callback === 'function')
6438 search: function(machine_guid, callback) {
6439 // SEARCH for the URLs of a machine:
6441 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,
6445 'Cache-Control': 'no-cache, no-store',
6446 'Pragma': 'no-cache'
6448 xhrFields: { withCredentials: true } // required for the cookie
6450 .done(function(data) {
6451 if(typeof data.status !== 'string' || data.status !== 'ok') {
6452 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6456 if(typeof callback === 'function')
6460 NETDATA.error(418, NETDATA.registry.server);
6462 if(typeof callback === 'function')
6467 switch: function(new_person_guid, callback) {
6470 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,
6474 'Cache-Control': 'no-cache, no-store',
6475 'Pragma': 'no-cache'
6477 xhrFields: { withCredentials: true } // required for the cookie
6479 .done(function(data) {
6480 if(typeof data.status !== 'string' || data.status !== 'ok') {
6481 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6485 if(typeof callback === 'function')
6489 NETDATA.error(414, NETDATA.registry.server);
6491 if(typeof callback === 'function')
6497 // ----------------------------------------------------------------------------------------------------------------
6500 if(typeof netdataPrepCallback === 'function')
6501 netdataPrepCallback();
6503 NETDATA.errorReset();
6504 NETDATA.loadRequiredCSS(0);
6506 NETDATA._loadjQuery(function() {
6507 NETDATA.loadRequiredJs(0, function() {
6508 if(typeof $().emulateTransitionEnd !== 'function') {
6509 // bootstrap is not available
6510 NETDATA.options.current.show_help = false;
6513 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6514 if(NETDATA.options.debug.main_loop === true)
6515 console.log('starting chart refresh thread');
6522 // window.NETDATA = NETDATA;
6523 // })(window, document);