1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
32 //(function(window, document, undefined) {
34 // ------------------------------------------------------------------------
35 // compatibility fixes
37 // fix IE issue with console
38 if(!window.console) { window.console = { log: function(){} }; }
40 // if string.endsWith is not defined, define it
41 if(typeof String.prototype.endsWith !== 'function') {
42 String.prototype.endsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(-s.length) === s;
48 // if string.startsWith is not defined, define it
49 if(typeof String.prototype.startsWith !== 'function') {
50 String.prototype.startsWith = function(s) {
51 if(s.length > this.length) return false;
52 return this.slice(s.length) === s;
57 var NETDATA = window.NETDATA || {};
59 NETDATA.name2id = function(s) {
68 // ----------------------------------------------------------------------------------------------------------------
69 // Detect the netdata server
71 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73 NETDATA._scriptSource = function() {
76 if(typeof document.currentScript !== 'undefined') {
77 script = document.currentScript;
80 var all_scripts = document.getElementsByTagName('script');
81 script = all_scripts[all_scripts.length - 1];
84 if (typeof script.getAttribute.length !== 'undefined')
87 script = script.getAttribute('src', -1);
92 if(typeof netdataServer !== 'undefined')
93 NETDATA.serverDefault = netdataServer;
95 var s = NETDATA._scriptSource();
96 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
98 console.log('WARNING: Cannot detect the URL of the netdata server.');
99 NETDATA.serverDefault = null;
103 if(NETDATA.serverDefault === null)
104 NETDATA.serverDefault = '';
105 else if(NETDATA.serverDefault.slice(-1) !== '/')
106 NETDATA.serverDefault += '/';
108 // default URLs for all the external files we need
109 // make them RELATIVE so that the whole thing can also be
110 // installed under a web server
111 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124 NETDATA.google_js = 'https://www.google.com/jsapi';
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161226-1',
130 background: '#FFFFFF',
131 foreground: '#000000',
134 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
135 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
136 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
137 '#329262', '#3B3EAC' ],
138 easypiechart_track: '#f0f0f0',
139 easypiechart_scale: '#dfe0e0',
140 gauge_pointer: '#C0C0C0',
141 gauge_stroke: '#F0F0F0',
142 gauge_gradient: false
145 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161218-2',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161226-1',
147 background: '#272b30',
148 foreground: '#C8C8C8',
151 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
152 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
153 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
154 '#a6a479', '#a66da8' ],
156 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
157 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
158 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
159 '#329262', '#3B3EFF' ],
160 easypiechart_track: '#373b40',
161 easypiechart_scale: '#373b40',
162 gauge_pointer: '#474b50',
163 gauge_stroke: '#373b40',
164 gauge_gradient: false
168 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169 NETDATA.themes.current = NETDATA.themes[netdataTheme];
171 NETDATA.themes.current = NETDATA.themes.white;
173 NETDATA.colors = NETDATA.themes.current.colors;
175 // these are the colors Google Charts are using
176 // we have them here to attempt emulate their look and feel on the other chart libraries
177 // http://there4.io/2012/05/02/google-chart-color-list/
178 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
182 // an alternative set
183 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
185 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
187 if(typeof netdataShowHelp === 'undefined')
188 netdataShowHelp = true;
190 if(typeof netdataShowAlarms === 'undefined')
191 netdataShowAlarms = false;
193 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194 netdataRegistryAfterMs = 1500;
196 if(typeof netdataRegistry === 'undefined') {
197 // backward compatibility
198 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199 netdataRegistry = true;
201 netdataRegistry = false;
203 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204 netdataRegistry = true;
206 // ----------------------------------------------------------------------------------------------------------------
207 // the defaults for all charts
209 // if the user does not specify any of these, the following will be used
211 NETDATA.chartDefaults = {
212 host: NETDATA.serverDefault, // the server to get data from
213 width: '100%', // the chart width - can be null
214 height: '100%', // the chart height - can be null
215 min_width: null, // the chart minimum width - can be null
216 library: 'dygraph', // the graphing library to use
217 method: 'average', // the grouping method
218 before: 0, // panning
219 after: -600, // panning
220 pixels_per_point: 1, // the detail of the chart
221 fill_luminance: 0.8 // luminance of colors in solit areas
224 // ----------------------------------------------------------------------------------------------------------------
228 pauseCallback: null, // a callback when we are really paused
230 pause: false, // when enabled we don't auto-refresh the charts
232 targets: null, // an array of all the state objects that are
233 // currently active (independently of their
234 // viewport visibility)
236 updated_dom: true, // when true, the DOM has been updated with
237 // new elements we have to check.
239 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
240 // rendering charts continiously.
241 // used with .current.fast_render_timeframe
243 page_is_visible: true, // when true, this page is visible
245 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
246 // auto-refresher for some time (when a chart is
247 // performing pan or zoom, we need to stop refreshing
248 // all other charts, to have the maximum speed for
249 // rendering the chart that is panned or zoomed).
250 // Used with .current.global_pan_sync_time
252 last_resized: Date.now(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false, // kill pending ajax page scroll
332 async_on_scroll: false, // sync/async onscroll handler
333 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
335 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
337 setOptionCallback: function() { ; }
345 chart_data_url: false,
346 chart_errors: false, // FIXME
354 NETDATA.statistics = {
357 refreshes_active_max: 0
361 // ----------------------------------------------------------------------------------------------------------------
362 // local storage options
364 NETDATA.localStorage = {
367 callback: {} // only used for resetting back to defaults
370 NETDATA.localStorageGet = function(key, def, callback) {
373 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
374 NETDATA.localStorage.default[key.toString()] = def;
375 NETDATA.localStorage.callback[key.toString()] = callback;
378 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
380 // console.log('localStorage: loading "' + key.toString() + '"');
381 ret = localStorage.getItem(key.toString());
382 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
383 if(ret === null || ret === 'undefined') {
384 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
385 localStorage.setItem(key.toString(), JSON.stringify(def));
389 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
390 ret = JSON.parse(ret);
391 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
395 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
400 if(typeof ret === 'undefined' || ret === 'undefined') {
401 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
405 NETDATA.localStorage.current[key.toString()] = ret;
409 NETDATA.localStorageSet = function(key, value, callback) {
410 if(typeof value === 'undefined' || value === 'undefined') {
411 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
414 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
415 NETDATA.localStorage.default[key.toString()] = value;
416 NETDATA.localStorage.current[key.toString()] = value;
417 NETDATA.localStorage.callback[key.toString()] = callback;
420 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
421 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
423 localStorage.setItem(key.toString(), JSON.stringify(value));
426 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
430 NETDATA.localStorage.current[key.toString()] = value;
434 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
436 if(typeof obj[i] === 'object') {
437 //console.log('object ' + prefix + '.' + i.toString());
438 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
442 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
446 NETDATA.setOption = function(key, value) {
447 if(key.toString() === 'setOptionCallback') {
448 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
449 NETDATA.options.current[key.toString()] = value;
450 NETDATA.options.current.setOptionCallback();
453 else if(NETDATA.options.current[key.toString()] !== value) {
454 var name = 'options.' + key.toString();
456 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
457 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
459 //console.log(NETDATA.localStorage);
460 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
461 //console.log(NETDATA.options);
462 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
464 if(typeof NETDATA.options.current.setOptionCallback === 'function')
465 NETDATA.options.current.setOptionCallback();
471 NETDATA.getOption = function(key) {
472 return NETDATA.options.current[key.toString()];
475 // read settings from local storage
476 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
478 // always start with this option enabled.
479 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
481 NETDATA.resetOptions = function() {
482 for(var i in NETDATA.localStorage.default) {
483 var a = i.split('.');
485 if(a[0] === 'options') {
486 if(a[1] === 'setOptionCallback') continue;
487 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
488 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
490 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
492 else if(a[0] === 'chart_heights') {
493 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
494 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
500 // ----------------------------------------------------------------------------------------------------------------
502 if(NETDATA.options.debug.main_loop === true)
503 console.log('welcome to NETDATA');
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
510 NETDATA.onscroll_updater_count = 0;
511 NETDATA.onscroll_updater_running = false;
512 NETDATA.onscroll_updater_last_run = 0;
513 NETDATA.onscroll_updater_watchdog = null;
514 NETDATA.onscroll_updater_max_duration = 0;
515 NETDATA.onscroll_updater_above_threshold_count = 0;
516 NETDATA.onscroll_updater = function() {
517 NETDATA.onscroll_updater_running = true;
518 NETDATA.onscroll_updater_count++;
519 var start = Date.now();
521 var targets = NETDATA.options.targets;
522 var len = targets.length;
524 // when the user scrolls he sees that we have
525 // hidden all the not-visible charts
526 // using this little function we try to switch
527 // the charts back to visible quickly
530 if(NETDATA.options.abort_ajax_on_scroll === true) {
531 // we have to cancel pending requests too
534 if (targets[len]._updating === true) {
535 if (typeof targets[len].xhr !== 'undefined') {
536 targets[len].xhr.abort();
537 targets[len].running = false;
538 targets[len]._updating = false;
540 targets[len].isVisible();
545 // just find which chart is visible
548 targets[len].isVisible();
551 var end = Date.now();
552 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
554 if(NETDATA.options.current.async_on_scroll === false) {
555 var dt = end - start;
556 if(dt > NETDATA.onscroll_updater_max_duration) {
557 // console.log('max onscroll event handler duration increased to ' + dt);
558 NETDATA.onscroll_updater_max_duration = dt;
561 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
562 // console.log('slow: ' + dt);
563 NETDATA.onscroll_updater_above_threshold_count++;
565 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
566 NETDATA.setOption('async_on_scroll', true);
567 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
572 NETDATA.onscroll_updater_last_run = start;
573 NETDATA.onscroll_updater_running = false;
576 NETDATA.onscroll = function() {
577 // console.log('onscroll');
579 NETDATA.options.last_page_scroll = Date.now();
580 NETDATA.options.auto_refresher_stop_until = 0;
582 if(NETDATA.options.targets === null) return;
584 if(NETDATA.options.current.async_on_scroll === true) {
586 if(NETDATA.onscroll_updater_running === false) {
587 NETDATA.onscroll_updater_running = true;
588 setTimeout(NETDATA.onscroll_updater, 0);
591 if(NETDATA.onscroll_updater_watchdog !== null)
592 clearTimeout(NETDATA.onscroll_updater_watchdog);
594 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
595 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
596 // console.log('watchdog');
597 NETDATA.onscroll_updater();
600 NETDATA.onscroll_updater_watchdog = null;
606 NETDATA.onscroll_updater();
610 window.onresize = NETDATA.onresize;
611 window.onscroll = NETDATA.onscroll;
613 // ----------------------------------------------------------------------------------------------------------------
616 NETDATA.errorCodes = {
617 100: { message: "Cannot load chart library", alert: true },
618 101: { message: "Cannot load jQuery", alert: true },
619 402: { message: "Chart library not found", alert: false },
620 403: { message: "Chart library not enabled/is failed", alert: false },
621 404: { message: "Chart not found", alert: false },
622 405: { message: "Cannot download charts index from server", alert: true },
623 406: { message: "Invalid charts index downloaded from server", alert: true },
624 407: { message: "Cannot HELLO netdata server", alert: false },
625 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
626 409: { message: "Cannot ACCESS netdata registry", alert: false },
627 410: { message: "Netdata registry ACCESS failed", alert: false },
628 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
629 412: { message: "Netdata registry DELETE failed", alert: false },
630 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
631 414: { message: "Netdata registry SWITCH failed", alert: false },
632 415: { message: "Netdata alarms download failed", alert: false },
633 416: { message: "Netdata alarms log download failed", alert: false },
634 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
635 418: { message: "Netdata registry SEARCH failed", alert: false }
637 NETDATA.errorLast = {
643 NETDATA.error = function(code, msg) {
644 NETDATA.errorLast.code = code;
645 NETDATA.errorLast.message = msg;
646 NETDATA.errorLast.datetime = Date.now();
648 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
651 if(typeof netdataErrorCallback === 'function') {
652 ret = netdataErrorCallback('system', code, msg);
655 if(ret && NETDATA.errorCodes[code].alert)
656 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
659 NETDATA.errorReset = function() {
660 NETDATA.errorLast.code = 0;
661 NETDATA.errorLast.message = "You are doing fine!";
662 NETDATA.errorLast.datetime = 0;
665 // ----------------------------------------------------------------------------------------------------------------
666 // commonMin & commonMax
668 NETDATA.commonMin = {
672 get: function(state) {
673 if(typeof state.__commonMin === 'undefined') {
674 // get the commonMin setting
675 var self = $(state.element);
676 state.__commonMin = self.data('common-min') || null;
679 var min = state.data.min;
680 var name = state.__commonMin;
683 // we don't need commonMin
684 //state.log('no need for commonMin');
688 var t = this.keys[name];
689 if(typeof t === 'undefined') {
691 this.keys[name] = {};
695 var uuid = state.uuid;
696 if(typeof t[uuid] !== 'undefined') {
697 if(t[uuid] === min) {
698 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
699 return this.latest[name];
701 else if(min < this.latest[name]) {
702 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
704 this.latest[name] = min;
712 // find the common min
715 if(t[i] < m) m = t[i];
717 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
718 this.latest[name] = m;
723 NETDATA.commonMax = {
727 get: function(state) {
728 if(typeof state.__commonMax === 'undefined') {
729 // get the commonMax setting
730 var self = $(state.element);
731 state.__commonMax = self.data('common-max') || null;
734 var max = state.data.max;
735 var name = state.__commonMax;
738 // we don't need commonMax
739 //state.log('no need for commonMax');
743 var t = this.keys[name];
744 if(typeof t === 'undefined') {
746 this.keys[name] = {};
750 var uuid = state.uuid;
751 if(typeof t[uuid] !== 'undefined') {
752 if(t[uuid] === max) {
753 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
754 return this.latest[name];
756 else if(max > this.latest[name]) {
757 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
759 this.latest[name] = max;
767 // find the common max
770 if(t[i] > m) m = t[i];
772 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
773 this.latest[name] = m;
778 // ----------------------------------------------------------------------------------------------------------------
781 // When multiple charts need the same chart, we avoid downloading it
782 // multiple times (and having it in browser memory multiple time)
783 // by using this registry.
785 // Every time we download a chart definition, we save it here with .add()
786 // Then we try to get it back with .get(). If that fails, we download it.
788 NETDATA.chartRegistry = {
791 fixid: function(id) {
792 return id.replace(/:/g, "_").replace(/\//g, "_");
795 add: function(host, id, data) {
796 host = this.fixid(host);
799 if(typeof this.charts[host] === 'undefined')
800 this.charts[host] = {};
802 //console.log('added ' + host + '/' + id);
803 this.charts[host][id] = data;
806 get: function(host, id) {
807 host = this.fixid(host);
810 if(typeof this.charts[host] === 'undefined')
813 if(typeof this.charts[host][id] === 'undefined')
816 //console.log('cached ' + host + '/' + id);
817 return this.charts[host][id];
820 downloadAll: function(host, callback) {
821 while(host.slice(-1) === '/')
822 host = host.substring(0, host.length - 1);
827 url: host + '/api/v1/charts',
830 xhrFields: { withCredentials: true } // required for the cookie
832 .done(function(data) {
834 var h = NETDATA.chartRegistry.fixid(host);
835 self.charts[h] = data.charts;
837 else NETDATA.error(406, host + '/api/v1/charts');
839 if(typeof callback === 'function')
843 NETDATA.error(405, host + '/api/v1/charts');
845 if(typeof callback === 'function')
851 // ----------------------------------------------------------------------------------------------------------------
852 // Global Pan and Zoom on charts
854 // Using this structure are synchronize all the charts, so that
855 // when you pan or zoom one, all others are automatically refreshed
856 // to the same timespan.
858 NETDATA.globalPanAndZoom = {
859 seq: 0, // timestamp ms
860 // every time a chart is panned or zoomed
861 // we set the timestamp here
862 // then we use it as a sequence number
863 // to find if other charts are syncronized
866 master: null, // the master chart (state), to which all others
869 force_before_ms: null, // the timespan to sync all other charts
870 force_after_ms: null,
875 setMaster: function(state, after, before) {
876 if(NETDATA.options.current.sync_pan_and_zoom === false)
879 if(this.master !== null && this.master !== state)
880 this.master.resetChart(true, true);
882 var now = Date.now();
885 this.force_after_ms = after;
886 this.force_before_ms = before;
887 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
889 if(typeof this.callback === 'function')
890 this.callback(true, after, before);
894 clearMaster: function() {
895 if(this.master !== null) {
896 var st = this.master;
903 this.force_after_ms = null;
904 this.force_before_ms = null;
905 NETDATA.options.auto_refresher_stop_until = 0;
907 if(typeof this.callback === 'function')
908 this.callback(false, 0, 0);
911 // is the given state the master of the global
912 // pan and zoom sync?
913 isMaster: function(state) {
914 if(this.master === state) return true;
918 // are we currently have a global pan and zoom sync?
919 isActive: function() {
920 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
924 // check if a chart, other than the master
925 // needs to be refreshed, due to the global pan and zoom
926 shouldBeAutoRefreshed: function(state) {
927 if(this.master === null || this.seq === 0)
930 //if(state.needsRecreation())
933 if(state.tm.pan_and_zoom_seq === this.seq)
940 // ----------------------------------------------------------------------------------------------------------------
941 // dimensions selection
944 // move color assignment to dimensions, here
946 dimensionStatus = function(parent, label, name_div, value_div, color) {
947 this.enabled = false;
948 this.parent = parent;
950 this.name_div = null;
951 this.value_div = null;
952 this.color = NETDATA.themes.current.foreground;
954 if(parent.unselected_count === 0)
955 this.selected = true;
957 this.selected = false;
959 this.setOptions(name_div, value_div, color);
962 dimensionStatus.prototype.invalidate = function() {
963 this.name_div = null;
964 this.value_div = null;
965 this.enabled = false;
968 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
971 if(this.name_div != name_div) {
972 this.name_div = name_div;
973 this.name_div.title = this.label;
974 this.name_div.style.color = this.color;
975 if(this.selected === false)
976 this.name_div.className = 'netdata-legend-name not-selected';
978 this.name_div.className = 'netdata-legend-name selected';
981 if(this.value_div != value_div) {
982 this.value_div = value_div;
983 this.value_div.title = this.label;
984 this.value_div.style.color = this.color;
985 if(this.selected === false)
986 this.value_div.className = 'netdata-legend-value not-selected';
988 this.value_div.className = 'netdata-legend-value selected';
995 dimensionStatus.prototype.setHandler = function() {
996 if(this.enabled === false) return;
1000 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1001 this.name_div.onclick = this.value_div.onclick = function(e) {
1003 if(ds.isSelected()) {
1005 if(e.shiftKey === true || e.ctrlKey === true) {
1006 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1009 if(ds.parent.countSelected() === 0)
1010 ds.parent.selectAll();
1013 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1014 if(ds.parent.countSelected() === 1) {
1015 ds.parent.selectAll();
1018 ds.parent.selectNone();
1024 // this is not selected
1025 if(e.shiftKey === true || e.ctrlKey === true) {
1026 // control or shift key is pressed -> select this too
1030 // no key is pressed -> select only this
1031 ds.parent.selectNone();
1036 ds.parent.state.redrawChart();
1040 dimensionStatus.prototype.select = function() {
1041 if(this.enabled === false) return;
1043 this.name_div.className = 'netdata-legend-name selected';
1044 this.value_div.className = 'netdata-legend-value selected';
1045 this.selected = true;
1048 dimensionStatus.prototype.unselect = function() {
1049 if(this.enabled === false) return;
1051 this.name_div.className = 'netdata-legend-name not-selected';
1052 this.value_div.className = 'netdata-legend-value hidden';
1053 this.selected = false;
1056 dimensionStatus.prototype.isSelected = function() {
1057 return(this.enabled === true && this.selected === true);
1060 // ----------------------------------------------------------------------------------------------------------------
1062 dimensionsVisibility = function(state) {
1065 this.dimensions = {};
1066 this.selected_count = 0;
1067 this.unselected_count = 0;
1070 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1071 if(typeof this.dimensions[label] === 'undefined') {
1073 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1076 this.dimensions[label].setOptions(name_div, value_div, color);
1078 return this.dimensions[label];
1081 dimensionsVisibility.prototype.dimensionGet = function(label) {
1082 return this.dimensions[label];
1085 dimensionsVisibility.prototype.invalidateAll = function() {
1086 for(var d in this.dimensions)
1087 this.dimensions[d].invalidate();
1090 dimensionsVisibility.prototype.selectAll = function() {
1091 for(var d in this.dimensions)
1092 this.dimensions[d].select();
1095 dimensionsVisibility.prototype.countSelected = function() {
1097 for(var d in this.dimensions)
1098 if(this.dimensions[d].isSelected()) i++;
1103 dimensionsVisibility.prototype.selectNone = function() {
1104 for(var d in this.dimensions)
1105 this.dimensions[d].unselect();
1108 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1109 var ret = new Array();
1110 this.selected_count = 0;
1111 this.unselected_count = 0;
1113 var len = array.length;
1115 var ds = this.dimensions[array[len]];
1116 if(typeof ds === 'undefined') {
1117 // console.log(array[i] + ' is not found');
1120 else if(ds.isSelected()) {
1122 this.selected_count++;
1126 this.unselected_count++;
1130 if(this.selected_count === 0 && this.unselected_count !== 0) {
1132 return this.selected2BooleanArray(array);
1139 // ----------------------------------------------------------------------------------------------------------------
1140 // global selection sync
1142 NETDATA.globalSelectionSync = {
1144 dont_sync_before: 0,
1149 if(this.state !== null)
1150 this.state.globalSelectionSyncStop();
1154 if(this.state !== null) {
1155 this.state.globalSelectionSyncDelay();
1160 // ----------------------------------------------------------------------------------------------------------------
1161 // Our state object, where all per-chart values are stored
1163 chartState = function(element) {
1164 var self = $(element);
1165 this.element = element;
1168 // all private functions should use 'that', instead of 'this'
1171 /* error() - private
1172 * show an error instead of the chart
1174 var error = function(msg) {
1177 if(typeof netdataErrorCallback === 'function') {
1178 ret = netdataErrorCallback('chart', that.id, msg);
1182 that.element.innerHTML = that.id + ': ' + msg;
1183 that.enabled = false;
1184 that.current = that.pan;
1188 // GUID - a unique identifier for the chart
1189 this.uuid = NETDATA.guid();
1191 // string - the name of chart
1192 this.id = self.data('netdata');
1194 // string - the key for localStorage settings
1195 this.settings_id = self.data('id') || null;
1197 // the user given dimensions of the element
1198 this.width = self.data('width') || NETDATA.chartDefaults.width;
1199 this.height = self.data('height') || NETDATA.chartDefaults.height;
1201 if(this.settings_id !== null) {
1202 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1203 // this is the callback that will be called
1204 // if and when the user resets all localStorage variables
1205 // to their defaults
1207 resizeChartToHeight(height);
1211 // string - the netdata server URL, without any path
1212 this.host = self.data('host') || NETDATA.chartDefaults.host;
1214 // make sure the host does not end with /
1215 // all netdata API requests use absolute paths
1216 while(this.host.slice(-1) === '/')
1217 this.host = this.host.substring(0, this.host.length - 1);
1219 // string - the grouping method requested by the user
1220 this.method = self.data('method') || NETDATA.chartDefaults.method;
1222 // the time-range requested by the user
1223 this.after = self.data('after') || NETDATA.chartDefaults.after;
1224 this.before = self.data('before') || NETDATA.chartDefaults.before;
1226 // the pixels per point requested by the user
1227 this.pixels_per_point = self.data('pixels-per-point') || 1;
1228 this.points = self.data('points') || null;
1230 // the dimensions requested by the user
1231 this.dimensions = self.data('dimensions') || null;
1233 // the chart library requested by the user
1234 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1236 // how many retries we have made to load chart data from the server
1237 this.retries_on_data_failures = 0;
1239 // object - the chart library used
1240 this.library = null;
1244 this.colors_assigned = {};
1245 this.colors_available = null;
1247 // the element already created by the user
1248 this.element_message = null;
1250 // the element with the chart
1251 this.element_chart = null;
1253 // the element with the legend of the chart (if created by us)
1254 this.element_legend = null;
1255 this.element_legend_childs = {
1265 this.chart_url = null; // string - the url to download chart info
1266 this.chart = null; // object - the chart as downloaded from the server
1268 this.title = self.data('title') || null; // the title of the chart
1269 this.units = self.data('units') || null; // the units of the chart dimensions
1270 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1271 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1273 this.running = false; // boolean - true when the chart is being refreshed now
1274 this.validated = false; // boolean - has the chart been validated?
1275 this.enabled = true; // boolean - is the chart enabled for refresh?
1276 this.paused = false; // boolean - is the chart paused for any reason?
1277 this.selected = false; // boolean - is the chart shown a selection?
1278 this.debug = false; // boolean - console.log() debug info about this chart
1280 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1281 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1282 this.requested_after = null; // milliseconds - the timestamp of the request after param
1283 this.requested_before = null; // milliseconds - the timestamp of the request before param
1284 this.requested_padding = null;
1285 this.view_after = 0;
1286 this.view_before = 0;
1288 this.value_decimal_detail = -1;
1290 var d = self.data('decimal-digits');
1291 if(typeof d === 'number') {
1292 this.value_decimal_detail = 1;
1294 this.value_decimal_detail *= 10;
1301 force_update_at: 0, // the timestamp to force the update at
1302 force_before_ms: null,
1303 force_after_ms: null
1308 force_update_at: 0, // the timestamp to force the update at
1309 force_before_ms: null,
1310 force_after_ms: null
1315 force_update_at: 0, // the timestamp to force the update at
1316 force_before_ms: null,
1317 force_after_ms: null
1320 // this is a pointer to one of the sub-classes below
1322 this.current = this.auto;
1324 // check the requested library is available
1325 // we don't initialize it here - it will be initialized when
1326 // this chart will be first used
1327 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1328 NETDATA.error(402, that.library_name);
1329 error('chart library "' + that.library_name + '" is not found');
1332 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1333 NETDATA.error(403, that.library_name);
1334 error('chart library "' + that.library_name + '" is not enabled');
1338 that.library = NETDATA.chartLibraries[that.library_name];
1340 // milliseconds - the time the last refresh took
1341 this.refresh_dt_ms = 0;
1343 // if we need to report the rendering speed
1344 // find the element that needs to be updated
1345 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1347 if(refresh_dt_element_name !== null)
1348 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1350 this.refresh_dt_element = null;
1352 this.dimensions_visibility = new dimensionsVisibility(this);
1354 this._updating = false;
1356 // ============================================================================================================
1357 // PRIVATE FUNCTIONS
1359 var createDOM = function() {
1360 if(that.enabled === false) return;
1362 if(that.element_message !== null) that.element_message.innerHTML = '';
1363 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1364 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1366 that.element.innerHTML = '';
1368 that.element_message = document.createElement('div');
1369 that.element_message.className = ' netdata-message hidden';
1370 that.element.appendChild(that.element_message);
1372 that.element_chart = document.createElement('div');
1373 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1374 that.element.appendChild(that.element_chart);
1376 if(that.hasLegend() === true) {
1377 that.element.className = "netdata-container-with-legend";
1378 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1380 that.element_legend = document.createElement('div');
1381 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1382 that.element.appendChild(that.element_legend);
1385 that.element.className = "netdata-container";
1386 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1388 that.element_legend = null;
1390 that.element_legend_childs.series = null;
1392 if(typeof(that.width) === 'string')
1393 $(that.element).css('width', that.width);
1394 else if(typeof(that.width) === 'number')
1395 $(that.element).css('width', that.width + 'px');
1397 if(typeof(that.library.aspect_ratio) === 'undefined') {
1398 if(typeof(that.height) === 'string')
1399 $(that.element).css('height', that.height);
1400 else if(typeof(that.height) === 'number')
1401 $(that.element).css('height', that.height + 'px');
1404 var w = that.element.offsetWidth;
1405 if(w === null || w === 0) {
1406 // the div is hidden
1407 // this will resize the chart when next viewed
1408 that.tm.last_resized = 0;
1411 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1414 if(NETDATA.chartDefaults.min_width !== null)
1415 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1417 that.tm.last_dom_created = Date.now();
1423 * initialize state variables
1424 * destroy all (possibly) created state elements
1425 * create the basic DOM for a chart
1427 var init = function() {
1428 if(that.enabled === false) return;
1430 that.paused = false;
1431 that.selected = false;
1433 that.chart_created = false; // boolean - is the library.create() been called?
1434 that.updates_counter = 0; // numeric - the number of refreshes made so far
1435 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1436 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1439 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1440 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1441 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1443 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1444 last_updated: 0, // the timestamp the chart last updated with data
1445 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1447 // Used with NETDATA.globalPanAndZoom.seq
1448 last_visible_check: 0, // the time we last checked if it is visible
1449 last_resized: 0, // the time the chart was resized
1450 last_hidden: 0, // the time the chart was hidden
1451 last_unhidden: 0, // the time the chart was unhidden
1452 last_autorefreshed: 0 // the time the chart was last refreshed
1455 that.data = null; // the last data as downloaded from the netdata server
1456 that.data_url = 'invalid://'; // string - the last url used to update the chart
1457 that.data_points = 0; // number - the number of points returned from netdata
1458 that.data_after = 0; // milliseconds - the first timestamp of the data
1459 that.data_before = 0; // milliseconds - the last timestamp of the data
1460 that.data_update_every = 0; // milliseconds - the frequency to update the data
1462 that.tm.last_initialized = Date.now();
1465 that.setMode('auto');
1468 var maxMessageFontSize = function() {
1469 // normally we want a font size, as tall as the element
1470 var h = that.element_message.clientHeight;
1472 // but give it some air, 20% let's say, or 5 pixels min
1473 var lost = Math.max(h * 0.2, 5);
1476 // center the text, vertically
1477 var paddingTop = (lost - 5) / 2;
1479 // but check the width too
1480 // it should fit 10 characters in it
1481 var w = that.element_message.clientWidth / 10;
1483 paddingTop += (h - w) / 2;
1487 // and don't make it too huge
1488 // 5% of the screen size is good
1489 if(h > screen.height / 20) {
1490 paddingTop += (h - (screen.height / 20)) / 2;
1491 h = screen.height / 20;
1495 that.element_message.style.fontSize = h.toString() + 'px';
1496 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1499 var showMessage = function(msg) {
1500 that.element_message.className = 'netdata-message';
1501 that.element_message.innerHTML = msg;
1502 that.element_message.style.fontSize = 'x-small';
1503 that.element_message.style.paddingTop = '0px';
1504 that.___messageHidden___ = undefined;
1507 var showMessageIcon = function(icon) {
1508 that.element_message.innerHTML = icon;
1509 that.element_message.className = 'netdata-message icon';
1510 maxMessageFontSize();
1511 that.___messageHidden___ = undefined;
1514 var hideMessage = function() {
1515 if(typeof that.___messageHidden___ === 'undefined') {
1516 that.___messageHidden___ = true;
1517 that.element_message.className = 'netdata-message hidden';
1521 var showRendering = function() {
1523 if(that.chart !== null) {
1524 if(that.chart.chart_type === 'line')
1525 icon = '<i class="fa fa-line-chart"></i>';
1527 icon = '<i class="fa fa-area-chart"></i>';
1530 icon = '<i class="fa fa-area-chart"></i>';
1532 showMessageIcon(icon + ' netdata');
1535 var showLoading = function() {
1536 if(that.chart_created === false) {
1537 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1543 var isHidden = function() {
1544 if(typeof that.___chartIsHidden___ !== 'undefined')
1550 // hide the chart, when it is not visible - called from isVisible()
1551 var hideChart = function() {
1552 // hide it, if it is not already hidden
1553 if(isHidden() === true) return;
1555 if(that.chart_created === true) {
1556 if(NETDATA.options.current.destroy_on_hide === true) {
1557 // we should destroy it
1562 that.element_chart.style.display = 'none';
1563 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1564 that.tm.last_hidden = Date.now();
1567 // This works, but I not sure there are no corner cases somewhere
1568 // so it is commented - if the user has memory issues he can
1569 // set Destroy on Hide for all charts
1570 // that.data = null;
1574 that.___chartIsHidden___ = true;
1577 // unhide the chart, when it is visible - called from isVisible()
1578 var unhideChart = function() {
1579 if(isHidden() === false) return;
1581 that.___chartIsHidden___ = undefined;
1582 that.updates_since_last_unhide = 0;
1584 if(that.chart_created === false) {
1585 // we need to re-initialize it, to show our background
1586 // logo in bootstrap tabs, until the chart loads
1590 that.tm.last_unhidden = Date.now();
1591 that.element_chart.style.display = '';
1592 if(that.element_legend !== null) that.element_legend.style.display = '';
1598 var canBeRendered = function() {
1599 if(isHidden() === true || that.isVisible(true) === false)
1605 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1606 var callChartLibraryUpdateSafely = function(data) {
1609 if(canBeRendered() === false)
1612 if(NETDATA.options.debug.chart_errors === true)
1613 status = that.library.update(that, data);
1616 status = that.library.update(that, data);
1623 if(status === false) {
1624 error('chart failed to be updated as ' + that.library_name);
1631 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1632 var callChartLibraryCreateSafely = function(data) {
1635 if(canBeRendered() === false)
1638 if(NETDATA.options.debug.chart_errors === true)
1639 status = that.library.create(that, data);
1642 status = that.library.create(that, data);
1649 if(status === false) {
1650 error('chart failed to be created as ' + that.library_name);
1654 that.chart_created = true;
1655 that.updates_since_last_creation = 0;
1659 // ----------------------------------------------------------------------------------------------------------------
1662 // resizeChart() - private
1663 // to be called just before the chart library to make sure that
1664 // a properly sized dom is available
1665 var resizeChart = function() {
1666 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1667 if(that.chart_created === false) return;
1669 if(that.needsRecreation()) {
1672 else if(typeof that.library.resize === 'function') {
1673 that.library.resize(that);
1675 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1676 $(that.element_legend_childs.nano).nanoScroller();
1678 maxMessageFontSize();
1681 that.tm.last_resized = Date.now();
1685 // this is the actual chart resize algorithm
1687 // - resize the entire container
1688 // - update the internal states
1689 // - resize the chart as the div changes height
1690 // - update the scrollbar of the legend
1691 var resizeChartToHeight = function(h) {
1693 that.element.style.height = h;
1695 if(that.settings_id !== null)
1696 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1698 var now = Date.now();
1699 NETDATA.options.last_page_scroll = now;
1700 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1703 that.tm.last_resized = 0;
1707 this.resizeHandler = function(e) {
1710 if(typeof this.event_resize === 'undefined'
1711 || this.event_resize.chart_original_w === 'undefined'
1712 || this.event_resize.chart_original_h === 'undefined')
1713 this.event_resize = {
1714 chart_original_w: this.element.clientWidth,
1715 chart_original_h: this.element.clientHeight,
1719 if(e.type === 'touchstart') {
1720 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1721 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1724 this.event_resize.mouse_start_x = e.clientX;
1725 this.event_resize.mouse_start_y = e.clientY;
1728 this.event_resize.chart_start_w = this.element.clientWidth;
1729 this.event_resize.chart_start_h = this.element.clientHeight;
1730 this.event_resize.chart_last_w = this.element.clientWidth;
1731 this.event_resize.chart_last_h = this.element.clientHeight;
1733 var now = Date.now();
1734 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1735 // double click / double tap event
1737 // the optimal height of the chart
1738 // showing the entire legend
1739 var optimal = this.event_resize.chart_last_h
1740 + this.element_legend_childs.content.scrollHeight
1741 - this.element_legend_childs.content.clientHeight;
1743 // if we are not optimal, be optimal
1744 if(this.event_resize.chart_last_h != optimal)
1745 resizeChartToHeight(optimal.toString() + 'px');
1747 // else if we do not have the original height
1748 // reset to the original height
1749 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1750 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1753 this.event_resize.last = now;
1755 // process movement event
1756 document.onmousemove =
1757 document.ontouchmove =
1758 this.element_legend_childs.resize_handler.onmousemove =
1759 this.element_legend_childs.resize_handler.ontouchmove =
1764 case 'mousemove': y = e.clientY; break;
1765 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1769 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1771 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1772 resizeChartToHeight(newH.toString() + 'px');
1773 that.event_resize.chart_last_h = newH;
1778 // process end event
1779 document.onmouseup =
1780 document.ontouchend =
1781 this.element_legend_childs.resize_handler.onmouseup =
1782 this.element_legend_childs.resize_handler.ontouchend =
1784 // remove all the hooks
1785 document.onmouseup =
1786 document.onmousemove =
1787 document.ontouchmove =
1788 document.ontouchend =
1789 that.element_legend_childs.resize_handler.onmousemove =
1790 that.element_legend_childs.resize_handler.ontouchmove =
1791 that.element_legend_childs.resize_handler.onmouseout =
1792 that.element_legend_childs.resize_handler.onmouseup =
1793 that.element_legend_childs.resize_handler.ontouchend =
1796 // allow auto-refreshes
1797 NETDATA.options.auto_refresher_stop_until = 0;
1803 var noDataToShow = function() {
1804 showMessageIcon('<i class="fa fa-warning"></i> empty');
1805 that.legendUpdateDOM();
1806 that.tm.last_autorefreshed = Date.now();
1807 // that.data_update_every = 30 * 1000;
1808 //that.element_chart.style.display = 'none';
1809 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1810 //that.___chartIsHidden___ = true;
1813 // ============================================================================================================
1816 this.error = function(msg) {
1820 this.setMode = function(m) {
1821 if(this.current !== null && this.current.name === m) return;
1824 this.current = this.auto;
1825 else if(m === 'pan')
1826 this.current = this.pan;
1827 else if(m === 'zoom')
1828 this.current = this.zoom;
1830 this.current = this.auto;
1832 this.current.force_update_at = 0;
1833 this.current.force_before_ms = null;
1834 this.current.force_after_ms = null;
1836 this.tm.last_mode_switch = Date.now();
1839 // ----------------------------------------------------------------------------------------------------------------
1840 // global selection sync
1842 // prevent to global selection sync for some time
1843 this.globalSelectionSyncDelay = function(ms) {
1844 if(NETDATA.options.current.sync_selection === false)
1847 if(typeof ms === 'number')
1848 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1850 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1853 // can we globally apply selection sync?
1854 this.globalSelectionSyncAbility = function() {
1855 if(NETDATA.options.current.sync_selection === false)
1858 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1864 this.globalSelectionSyncIsMaster = function() {
1865 if(NETDATA.globalSelectionSync.state === this)
1871 // this chart is the master of the global selection sync
1872 this.globalSelectionSyncBeMaster = function() {
1874 if(this.globalSelectionSyncIsMaster()) {
1875 if(this.debug === true)
1876 this.log('sync: I am the master already.');
1881 if(NETDATA.globalSelectionSync.state) {
1882 if(this.debug === true)
1883 this.log('sync: I am not the sync master. Resetting global sync.');
1885 this.globalSelectionSyncStop();
1888 // become the master
1889 if(this.debug === true)
1890 this.log('sync: becoming sync master.');
1892 this.selected = true;
1893 NETDATA.globalSelectionSync.state = this;
1895 // find the all slaves
1896 var targets = NETDATA.options.targets;
1897 var len = targets.length;
1902 if(this.debug === true)
1903 st.log('sync: not adding me to sync');
1905 else if(st.globalSelectionSyncIsEligible()) {
1906 if(this.debug === true)
1907 st.log('sync: adding to sync as slave');
1909 st.globalSelectionSyncBeSlave();
1913 // this.globalSelectionSyncDelay(100);
1916 // can the chart participate to the global selection sync as a slave?
1917 this.globalSelectionSyncIsEligible = function() {
1918 if(this.enabled === true
1919 && this.library !== null
1920 && typeof this.library.setSelection === 'function'
1921 && this.isVisible() === true
1922 && this.chart_created === true)
1928 // this chart becomes a slave of the global selection sync
1929 this.globalSelectionSyncBeSlave = function() {
1930 if(NETDATA.globalSelectionSync.state !== this)
1931 NETDATA.globalSelectionSync.slaves.push(this);
1934 // sync all the visible charts to the given time
1935 // this is to be called from the chart libraries
1936 this.globalSelectionSync = function(t) {
1937 if(this.globalSelectionSyncAbility() === false) {
1938 if(this.debug === true)
1939 this.log('sync: cannot sync (yet?).');
1944 if(this.globalSelectionSyncIsMaster() === false) {
1945 if(this.debug === true)
1946 this.log('sync: trying to be sync master.');
1948 this.globalSelectionSyncBeMaster();
1950 if(this.globalSelectionSyncAbility() === false) {
1951 if(this.debug === true)
1952 this.log('sync: cannot sync (yet?).');
1958 NETDATA.globalSelectionSync.last_t = t;
1959 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1964 // stop syncing all charts to the given time
1965 this.globalSelectionSyncStop = function() {
1966 if(NETDATA.globalSelectionSync.slaves.length) {
1967 if(this.debug === true)
1968 this.log('sync: cleaning up...');
1970 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1972 if(that.debug === true)
1973 st.log('sync: not adding me to sync stop');
1976 if(that.debug === true)
1977 st.log('sync: removed slave from sync');
1979 st.clearSelection();
1983 NETDATA.globalSelectionSync.last_t = 0;
1984 NETDATA.globalSelectionSync.slaves = [];
1985 NETDATA.globalSelectionSync.state = null;
1988 this.clearSelection();
1991 this.setSelection = function(t) {
1992 if(typeof this.library.setSelection === 'function') {
1993 if(this.library.setSelection(this, t) === true)
1994 this.selected = true;
1996 this.selected = false;
1998 else this.selected = true;
2000 if(this.selected === true && this.debug === true)
2001 this.log('selection set to ' + t.toString());
2003 return this.selected;
2006 this.clearSelection = function() {
2007 if(this.selected === true) {
2008 if(typeof this.library.clearSelection === 'function') {
2009 if(this.library.clearSelection(this) === true)
2010 this.selected = false;
2012 this.selected = true;
2014 else this.selected = false;
2016 if(this.selected === false && this.debug === true)
2017 this.log('selection cleared');
2022 return this.selected;
2025 // find if a timestamp (ms) is shown in the current chart
2026 this.timeIsVisible = function(t) {
2027 if(t >= this.data_after && t <= this.data_before)
2032 this.calculateRowForTime = function(t) {
2033 if(this.timeIsVisible(t) === false) return -1;
2034 return Math.floor((t - this.data_after) / this.data_update_every);
2037 // ----------------------------------------------------------------------------------------------------------------
2040 this.log = function(msg) {
2041 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2044 this.pauseChart = function() {
2045 if(this.paused === false) {
2046 if(this.debug === true)
2047 this.log('pauseChart()');
2053 this.unpauseChart = function() {
2054 if(this.paused === true) {
2055 if(this.debug === true)
2056 this.log('unpauseChart()');
2058 this.paused = false;
2062 this.resetChart = function(dont_clear_master, dont_update) {
2063 if(this.debug === true)
2064 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2066 if(typeof dont_clear_master === 'undefined')
2067 dont_clear_master = false;
2069 if(typeof dont_update === 'undefined')
2070 dont_update = false;
2072 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2073 if(this.debug === true)
2074 this.log('resetChart() diverting to clearMaster().');
2075 // this will call us back with master === true
2076 NETDATA.globalPanAndZoom.clearMaster();
2080 this.clearSelection();
2082 this.tm.pan_and_zoom_seq = 0;
2084 this.setMode('auto');
2085 this.current.force_update_at = 0;
2086 this.current.force_before_ms = null;
2087 this.current.force_after_ms = null;
2088 this.tm.last_autorefreshed = 0;
2089 this.paused = false;
2090 this.selected = false;
2091 this.enabled = true;
2092 // this.debug = false;
2094 // do not update the chart here
2095 // or the chart will flip-flop when it is the master
2096 // of a selection sync and another chart becomes
2099 if(dont_update !== true && this.isVisible() === true) {
2104 this.updateChartPanOrZoom = function(after, before) {
2105 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2108 if(this.debug === true)
2111 if(before < after) {
2112 if(this.debug === true)
2113 this.log(logme + 'flipped parameters, rejecting it.');
2118 if(typeof this.fixed_min_duration === 'undefined')
2119 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2121 var min_duration = this.fixed_min_duration;
2122 var current_duration = Math.round(this.view_before - this.view_after);
2124 // round the numbers
2125 after = Math.round(after);
2126 before = Math.round(before);
2128 // align them to update_every
2129 // stretching them further away
2130 after -= after % this.data_update_every;
2131 before += this.data_update_every - (before % this.data_update_every);
2133 // the final wanted duration
2134 var wanted_duration = before - after;
2136 // to allow panning, accept just a point below our minimum
2137 if((current_duration - this.data_update_every) < min_duration)
2138 min_duration = current_duration - this.data_update_every;
2140 // we do it, but we adjust to minimum size and return false
2141 // when the wanted size is below the current and the minimum
2143 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2144 if(this.debug === true)
2145 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2147 min_duration = this.fixed_min_duration;
2149 var dt = (min_duration - wanted_duration) / 2;
2152 wanted_duration = before - after;
2156 var tolerance = this.data_update_every * 2;
2157 var movement = Math.abs(before - this.view_before);
2159 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2160 if(this.debug === true)
2161 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2165 if(this.current.name === 'auto') {
2166 this.log(logme + 'caller called me with mode: ' + this.current.name);
2167 this.setMode('pan');
2170 if(this.debug === true)
2171 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2173 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2174 this.current.force_after_ms = after;
2175 this.current.force_before_ms = before;
2176 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2180 this.legendFormatValue = function(value) {
2181 if(value === null || value === 'undefined') return '-';
2182 if(typeof value !== 'number') return value;
2184 if(this.value_decimal_detail !== -1)
2185 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2187 var abs = Math.abs(value);
2188 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2189 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2190 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2191 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2192 return (Math.round(value * 10000) / 10000).toLocaleString();
2195 this.legendSetLabelValue = function(label, value) {
2196 var series = this.element_legend_childs.series[label];
2197 if(typeof series === 'undefined') return;
2198 if(series.value === null && series.user === null) return;
2201 // this slows down firefox and edge significantly
2202 // since it requires to use innerHTML(), instead of innerText()
2204 // if the value has not changed, skip DOM update
2205 //if(series.last === value) return;
2208 if(typeof value === 'number') {
2209 var v = Math.abs(value);
2210 s = r = this.legendFormatValue(value);
2212 if(typeof series.last === 'number') {
2213 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2214 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2215 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2217 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2227 series.last = value;
2231 var s = this.legendFormatValue(value);
2233 // caching: do not update the update to show the same value again
2234 if(s === series.last_shown_value) return;
2235 series.last_shown_value = s;
2237 if(series.value !== null) series.value.innerText = s;
2238 if(series.user !== null) series.user.innerText = s;
2241 this.__legendSetDateString = function(date) {
2242 if(date !== this.__last_shown_legend_date) {
2243 this.element_legend_childs.title_date.innerText = date;
2244 this.__last_shown_legend_date = date;
2248 this.__legendSetTimeString = function(time) {
2249 if(time !== this.__last_shown_legend_time) {
2250 this.element_legend_childs.title_time.innerText = time;
2251 this.__last_shown_legend_time = time;
2255 this.__legendSetUnitsString = function(units) {
2256 if(units !== this.__last_shown_legend_units) {
2257 this.element_legend_childs.title_units.innerText = units;
2258 this.__last_shown_legend_units = units;
2262 this.legendSetDate = function(ms) {
2263 if(typeof ms !== 'number') {
2264 this.legendShowUndefined();
2268 var d = new Date(ms);
2270 if(this.element_legend_childs.title_date)
2271 this.__legendSetDateString(d.toLocaleDateString());
2273 if(this.element_legend_childs.title_time)
2274 this.__legendSetTimeString(d.toLocaleTimeString());
2276 if(this.element_legend_childs.title_units)
2277 this.__legendSetUnitsString(this.units)
2280 this.legendShowUndefined = function() {
2281 if(this.element_legend_childs.title_date)
2282 this.__legendSetDateString(' ');
2284 if(this.element_legend_childs.title_time)
2285 this.__legendSetTimeString(this.chart.name);
2287 if(this.element_legend_childs.title_units)
2288 this.__legendSetUnitsString(' ')
2290 if(this.data && this.element_legend_childs.series !== null) {
2291 var labels = this.data.dimension_names;
2292 var i = labels.length;
2294 var label = labels[i];
2296 if(typeof label === 'undefined') continue;
2297 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2298 this.legendSetLabelValue(label, null);
2303 this.legendShowLatestValues = function() {
2304 if(this.chart === null) return;
2305 if(this.selected) return;
2307 if(this.data === null || this.element_legend_childs.series === null) {
2308 this.legendShowUndefined();
2312 var show_undefined = true;
2313 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2314 show_undefined = false;
2316 if(show_undefined) {
2317 this.legendShowUndefined();
2321 this.legendSetDate(this.view_before);
2323 var labels = this.data.dimension_names;
2324 var i = labels.length;
2326 var label = labels[i];
2328 if(typeof label === 'undefined') continue;
2329 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2332 this.legendSetLabelValue(label, null);
2334 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2338 this.legendReset = function() {
2339 this.legendShowLatestValues();
2342 // this should be called just ONCE per dimension per chart
2343 this._chartDimensionColor = function(label) {
2344 if(this.colors === null) this.chartColors();
2346 if(typeof this.colors_assigned[label] === 'undefined') {
2347 if(this.colors_available.length === 0) {
2348 var len = NETDATA.themes.current.colors.length;
2350 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2353 this.colors_assigned[label] = this.colors_available.shift();
2355 if(this.debug === true)
2356 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2359 if(this.debug === true)
2360 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2363 this.colors.push(this.colors_assigned[label]);
2364 return this.colors_assigned[label];
2367 this.chartColors = function() {
2368 if(this.colors !== null) return this.colors;
2370 this.colors = new Array();
2371 this.colors_available = new Array();
2373 // add the standard colors
2374 var len = NETDATA.themes.current.colors.length;
2376 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2378 // add the user supplied colors
2379 var c = $(this.element).data('colors');
2380 // this.log('read colors: ' + c);
2381 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2382 if(typeof c !== 'string') {
2383 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2393 this.colors_available.unshift(c[len]);
2394 // this.log('adding color: ' + c[len]);
2403 this.legendUpdateDOM = function() {
2406 // check that the legend DOM is up to date for the downloaded dimensions
2407 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2408 // this.log('the legend does not have any series - requesting legend update');
2411 else if(this.data === null) {
2412 // this.log('the chart does not have any data - requesting legend update');
2415 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2419 var labels = this.data.dimension_names.toString();
2420 if(labels !== this.element_legend_childs.series.labels_key) {
2423 if(this.debug === true)
2424 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2428 if(needed === false) {
2429 // make sure colors available
2432 // do we have to update the current values?
2433 // we do this, only when the visible chart is current
2434 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2435 if(this.debug === true)
2436 this.log('chart is in latest position... updating values on legend...');
2438 //var labels = this.data.dimension_names;
2439 //var i = labels.length;
2441 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2445 if(this.colors === null) {
2446 // this is the first time we update the chart
2447 // let's assign colors to all dimensions
2448 if(this.library.track_colors() === true)
2449 for(var dim in this.chart.dimensions)
2450 this._chartDimensionColor(this.chart.dimensions[dim].name);
2452 // we will re-generate the colors for the chart
2453 // based on the selected dimensions
2456 if(this.debug === true)
2457 this.log('updating Legend DOM');
2459 // mark all dimensions as invalid
2460 this.dimensions_visibility.invalidateAll();
2462 var genLabel = function(state, parent, dim, name, count) {
2463 var color = state._chartDimensionColor(name);
2465 var user_element = null;
2466 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2467 if(user_id === null)
2468 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2469 if(user_id !== null) {
2470 user_element = document.getElementById(user_id) || null;
2471 if (user_element === null)
2472 state.log('Cannot find element with id: ' + user_id);
2475 state.element_legend_childs.series[name] = {
2476 name: document.createElement('span'),
2477 value: document.createElement('span'),
2480 last_shown_value: null
2483 var label = state.element_legend_childs.series[name];
2485 // create the dimension visibility tracking for this label
2486 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2488 var rgb = NETDATA.colorHex2Rgb(color);
2489 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2490 + state.chart.chart_type
2491 + '" style="background-color: '
2492 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2493 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2495 var text = document.createTextNode(' ' + name);
2496 label.name.appendChild(text);
2499 parent.appendChild(document.createElement('br'));
2501 parent.appendChild(label.name);
2502 parent.appendChild(label.value);
2505 var content = document.createElement('div');
2507 if(this.hasLegend()) {
2508 this.element_legend_childs = {
2510 resize_handler: document.createElement('div'),
2511 toolbox: document.createElement('div'),
2512 toolbox_left: document.createElement('div'),
2513 toolbox_right: document.createElement('div'),
2514 toolbox_reset: document.createElement('div'),
2515 toolbox_zoomin: document.createElement('div'),
2516 toolbox_zoomout: document.createElement('div'),
2517 toolbox_volume: document.createElement('div'),
2518 title_date: document.createElement('span'),
2519 title_time: document.createElement('span'),
2520 title_units: document.createElement('span'),
2521 nano: document.createElement('div'),
2523 paneClass: 'netdata-legend-series-pane',
2524 sliderClass: 'netdata-legend-series-slider',
2525 contentClass: 'netdata-legend-series-content',
2526 enabledClass: '__enabled',
2527 flashedClass: '__flashed',
2528 activeClass: '__active',
2530 alwaysVisible: true,
2536 this.element_legend.innerHTML = '';
2538 if(this.library.toolboxPanAndZoom !== null) {
2540 function get_pan_and_zoom_step(event) {
2542 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2544 else if (event.shiftKey)
2545 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2547 else if (event.altKey)
2548 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2551 return NETDATA.options.current.pan_and_zoom_factor;
2554 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2555 this.element.appendChild(this.element_legend_childs.toolbox);
2557 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2558 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2559 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2560 this.element_legend_childs.toolbox_left.onclick = function(e) {
2563 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2564 var before = that.view_before - step;
2565 var after = that.view_after - step;
2566 if(after >= that.netdata_first)
2567 that.library.toolboxPanAndZoom(that, after, before);
2569 if(NETDATA.options.current.show_help === true)
2570 $(this.element_legend_childs.toolbox_left).popover({
2575 placement: 'bottom',
2576 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2578 content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2582 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2583 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2584 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2585 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2587 NETDATA.resetAllCharts(that);
2589 if(NETDATA.options.current.show_help === true)
2590 $(this.element_legend_childs.toolbox_reset).popover({
2595 placement: 'bottom',
2596 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2597 title: 'Chart Reset',
2598 content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2601 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2602 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2603 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2604 this.element_legend_childs.toolbox_right.onclick = function(e) {
2606 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2607 var before = that.view_before + step;
2608 var after = that.view_after + step;
2609 if(before <= that.netdata_last)
2610 that.library.toolboxPanAndZoom(that, after, before);
2612 if(NETDATA.options.current.show_help === true)
2613 $(this.element_legend_childs.toolbox_right).popover({
2618 placement: 'bottom',
2619 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2621 content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2625 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2626 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2627 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2628 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2630 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2631 var before = that.view_before - dt;
2632 var after = that.view_after + dt;
2633 that.library.toolboxPanAndZoom(that, after, before);
2635 if(NETDATA.options.current.show_help === true)
2636 $(this.element_legend_childs.toolbox_zoomin).popover({
2641 placement: 'bottom',
2642 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2643 title: 'Chart Zoom In',
2644 content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2647 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2648 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2649 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2650 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2652 var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2653 var before = that.view_before + dt;
2654 var after = that.view_after - dt;
2656 that.library.toolboxPanAndZoom(that, after, before);
2658 if(NETDATA.options.current.show_help === true)
2659 $(this.element_legend_childs.toolbox_zoomout).popover({
2664 placement: 'bottom',
2665 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2666 title: 'Chart Zoom Out',
2667 content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2670 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2671 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2672 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2673 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2674 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2675 //e.preventDefault();
2676 //alert('clicked toolbox_volume on ' + that.id);
2680 this.element_legend_childs.toolbox = null;
2681 this.element_legend_childs.toolbox_left = null;
2682 this.element_legend_childs.toolbox_reset = null;
2683 this.element_legend_childs.toolbox_right = null;
2684 this.element_legend_childs.toolbox_zoomin = null;
2685 this.element_legend_childs.toolbox_zoomout = null;
2686 this.element_legend_childs.toolbox_volume = null;
2689 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2690 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2691 this.element.appendChild(this.element_legend_childs.resize_handler);
2692 if(NETDATA.options.current.show_help === true)
2693 $(this.element_legend_childs.resize_handler).popover({
2698 placement: 'bottom',
2699 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2700 title: 'Chart Resize',
2701 content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2705 this.element_legend_childs.resize_handler.onmousedown =
2707 that.resizeHandler(e);
2711 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2712 that.resizeHandler(e);
2715 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2716 this.element_legend.appendChild(this.element_legend_childs.title_date);
2718 this.element_legend.appendChild(document.createElement('br'));
2720 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2721 this.element_legend.appendChild(this.element_legend_childs.title_time);
2723 this.element_legend.appendChild(document.createElement('br'));
2725 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2726 this.element_legend.appendChild(this.element_legend_childs.title_units);
2728 this.element_legend.appendChild(document.createElement('br'));
2730 this.element_legend_childs.nano.className = 'netdata-legend-series';
2731 this.element_legend.appendChild(this.element_legend_childs.nano);
2733 content.className = 'netdata-legend-series-content';
2734 this.element_legend_childs.nano.appendChild(content);
2736 if(NETDATA.options.current.show_help === true)
2737 $(content).popover({
2742 placement: 'bottom',
2743 title: 'Chart Legend',
2744 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2745 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2749 this.element_legend_childs = {
2751 resize_handler: null,
2754 toolbox_right: null,
2755 toolbox_reset: null,
2756 toolbox_zoomin: null,
2757 toolbox_zoomout: null,
2758 toolbox_volume: null,
2769 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2770 if(this.debug === true)
2771 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2773 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2774 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2778 var tmp = new Array();
2779 for(var dim in this.chart.dimensions) {
2780 tmp.push(this.chart.dimensions[dim].name);
2781 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2783 this.element_legend_childs.series.labels_key = tmp.toString();
2784 if(this.debug === true)
2785 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2788 // create a hidden div to be used for hidding
2789 // the original legend of the chart library
2790 var el = document.createElement('div');
2791 if(this.element_legend !== null)
2792 this.element_legend.appendChild(el);
2793 el.style.display = 'none';
2795 this.element_legend_childs.hidden = document.createElement('div');
2796 el.appendChild(this.element_legend_childs.hidden);
2798 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2799 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2801 this.legendShowLatestValues();
2804 this.hasLegend = function() {
2805 if(typeof this.___hasLegendCache___ !== 'undefined')
2806 return this.___hasLegendCache___;
2809 if(this.library && this.library.legend(this) === 'right-side') {
2810 var legend = $(this.element).data('legend') || 'yes';
2811 if(legend === 'yes') leg = true;
2814 this.___hasLegendCache___ = leg;
2818 this.legendWidth = function() {
2819 return (this.hasLegend())?140:0;
2822 this.legendHeight = function() {
2823 return $(this.element).height();
2826 this.chartWidth = function() {
2827 return $(this.element).width() - this.legendWidth();
2830 this.chartHeight = function() {
2831 return $(this.element).height();
2834 this.chartPixelsPerPoint = function() {
2835 // force an options provided detail
2836 var px = this.pixels_per_point;
2838 if(this.library && px < this.library.pixels_per_point(this))
2839 px = this.library.pixels_per_point(this);
2841 if(px < NETDATA.options.current.pixels_per_point)
2842 px = NETDATA.options.current.pixels_per_point;
2847 this.needsRecreation = function() {
2849 this.chart_created === true
2851 && this.library.autoresize() === false
2852 && this.tm.last_resized < NETDATA.options.last_resized
2856 this.chartURL = function() {
2857 var after, before, points_multiplier = 1;
2858 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2859 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2861 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2862 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2863 this.view_after = after * 1000;
2864 this.view_before = before * 1000;
2866 this.requested_padding = null;
2867 points_multiplier = 1;
2869 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2870 this.tm.pan_and_zoom_seq = 0;
2872 before = Math.round(this.current.force_before_ms / 1000);
2873 after = Math.round(this.current.force_after_ms / 1000);
2874 this.view_after = after * 1000;
2875 this.view_before = before * 1000;
2877 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2878 this.requested_padding = Math.round((before - after) / 2);
2879 after -= this.requested_padding;
2880 before += this.requested_padding;
2881 this.requested_padding *= 1000;
2882 points_multiplier = 2;
2885 this.current.force_before_ms = null;
2886 this.current.force_after_ms = null;
2889 this.tm.pan_and_zoom_seq = 0;
2891 before = this.before;
2893 this.view_after = after * 1000;
2894 this.view_before = before * 1000;
2896 this.requested_padding = null;
2897 points_multiplier = 1;
2900 this.requested_after = after * 1000;
2901 this.requested_before = before * 1000;
2903 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2905 // build the data URL
2906 this.data_url = this.host + this.chart.data_url;
2907 this.data_url += "&format=" + this.library.format();
2908 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2909 this.data_url += "&group=" + this.method;
2911 if(this.override_options !== null)
2912 this.data_url += "&options=" + this.override_options.toString();
2914 this.data_url += "&options=" + this.library.options(this);
2916 this.data_url += '|jsonwrap';
2918 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2919 this.data_url += '|nonzero';
2921 if(this.append_options !== null)
2922 this.data_url += '|' + this.append_options.toString();
2925 this.data_url += "&after=" + after.toString();
2928 this.data_url += "&before=" + before.toString();
2931 this.data_url += "&dimensions=" + this.dimensions;
2933 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2934 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2937 this.redrawChart = function() {
2938 if(this.data !== null)
2939 this.updateChartWithData(this.data);
2942 this.updateChartWithData = function(data) {
2943 if(this.debug === true)
2944 this.log('updateChartWithData() called.');
2946 // this may force the chart to be re-created
2950 this.updates_counter++;
2951 this.updates_since_last_unhide++;
2952 this.updates_since_last_creation++;
2954 var started = Date.now();
2956 // if the result is JSON, find the latest update-every
2957 this.data_update_every = data.view_update_every * 1000;
2958 this.data_after = data.after * 1000;
2959 this.data_before = data.before * 1000;
2960 this.netdata_first = data.first_entry * 1000;
2961 this.netdata_last = data.last_entry * 1000;
2962 this.data_points = data.points;
2965 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2966 if(this.view_after < this.data_after) {
2967 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2968 this.view_after = this.data_after;
2971 if(this.view_before > this.data_before) {
2972 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2973 this.view_before = this.data_before;
2977 this.view_after = this.data_after;
2978 this.view_before = this.data_before;
2981 if(this.debug === true) {
2982 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2984 if(this.current.force_after_ms)
2985 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2987 this.log('STATUS: forced : unset');
2989 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2990 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2991 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2992 this.log('STATUS: points : ' + (this.data_points).toString());
2995 if(this.data_points === 0) {
3000 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3001 if(this.debug === true)
3002 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3004 this.chart_created = false;
3007 // check and update the legend
3008 this.legendUpdateDOM();
3010 if(this.chart_created === true
3011 && typeof this.library.update === 'function') {
3013 if(this.debug === true)
3014 this.log('updating chart...');
3016 if(callChartLibraryUpdateSafely(data) === false)
3020 if(this.debug === true)
3021 this.log('creating chart...');
3023 if(callChartLibraryCreateSafely(data) === false)
3027 this.legendShowLatestValues();
3028 if(this.selected === true)
3029 NETDATA.globalSelectionSync.stop();
3031 // update the performance counters
3032 var now = Date.now();
3033 this.tm.last_updated = now;
3035 // don't update last_autorefreshed if this chart is
3036 // forced to be updated with global PanAndZoom
3037 if(NETDATA.globalPanAndZoom.isActive())
3038 this.tm.last_autorefreshed = 0;
3040 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3041 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3043 this.tm.last_autorefreshed = now;
3046 this.refresh_dt_ms = now - started;
3047 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3049 if(this.refresh_dt_element !== null)
3050 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3053 this.updateChart = function(callback) {
3054 if(this.debug === true)
3055 this.log('updateChart() called.');
3057 if(this._updating === true) {
3058 if(this.debug === true)
3059 this.log('I am already updating...');
3061 if(typeof callback === 'function') callback();
3065 // due to late initialization of charts and libraries
3066 // we need to check this too
3067 if(this.enabled === false) {
3068 if(this.debug === true)
3069 this.log('I am not enabled');
3071 if(typeof callback === 'function') callback();
3075 if(canBeRendered() === false) {
3076 if(typeof callback === 'function') callback();
3080 if(this.chart === null) {
3081 this.getChart(function() { that.updateChart(callback); });
3085 if(this.library.initialized === false) {
3086 if(this.library.enabled === true) {
3087 this.library.initialize(function() { that.updateChart(callback); });
3091 error('chart library "' + this.library_name + '" is not available.');
3092 if(typeof callback === 'function') callback();
3097 this.clearSelection();
3100 if(this.debug === true)
3101 this.log('updating from ' + this.data_url);
3103 NETDATA.statistics.refreshes_total++;
3104 NETDATA.statistics.refreshes_active++;
3106 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3107 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3109 this._updating = true;
3111 this.xhr = $.ajax( {
3116 'Cache-Control': 'no-cache, no-store',
3117 'Pragma': 'no-cache'
3119 xhrFields: { withCredentials: true } // required for the cookie
3121 .done(function(data) {
3122 that.xhr = undefined;
3123 that.retries_on_data_failures = 0;
3125 if(that.debug === true)
3126 that.log('data received. updating chart.');
3128 that.updateChartWithData(data);
3130 .fail(function(msg) {
3131 that.xhr = undefined;
3133 if(msg.statusText !== 'abort') {
3134 that.retries_on_data_failures++;
3135 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3136 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3137 that.retries_on_data_failures = 0;
3138 error('data download failed for url: ' + that.data_url);
3141 that.tm.last_autorefreshed = Date.now();
3142 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3146 .always(function() {
3147 that.xhr = undefined;
3149 NETDATA.statistics.refreshes_active--;
3150 that._updating = false;
3151 if(typeof callback === 'function') callback();
3157 this.isVisible = function(nocache) {
3158 if(typeof nocache === 'undefined')
3161 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3163 // caching - we do not evaluate the charts visibility
3164 // if the page has not been scrolled since the last check
3165 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3166 return this.___isVisible___;
3168 this.tm.last_visible_check = Date.now();
3170 var wh = window.innerHeight;
3171 var x = this.element.getBoundingClientRect();
3175 if(x.width === 0 || x.height === 0) {
3177 this.___isVisible___ = false;
3178 return this.___isVisible___;
3181 if(x.top < 0 && -x.top > x.height) {
3182 // the chart is entirely above
3183 ret = -x.top - x.height;
3185 else if(x.top > wh) {
3186 // the chart is entirely below
3190 if(ret > tolerance) {
3191 // the chart is too far
3194 this.___isVisible___ = false;
3195 return this.___isVisible___;
3198 // the chart is inside or very close
3201 this.___isVisible___ = true;
3202 return this.___isVisible___;
3206 this.isAutoRefreshable = function() {
3207 return (this.current.autorefresh);
3210 this.canBeAutoRefreshed = function() {
3211 var now = Date.now();
3213 if(this.running === true) {
3214 if(this.debug === true)
3215 this.log('I am already running');
3220 if(this.enabled === false) {
3221 if(this.debug === true)
3222 this.log('I am not enabled');
3227 if(this.library === null || this.library.enabled === false) {
3228 error('charting library "' + this.library_name + '" is not available');
3229 if(this.debug === true)
3230 this.log('My chart library ' + this.library_name + ' is not available');
3235 if(this.isVisible() === false) {
3236 if(NETDATA.options.debug.visibility === true || this.debug === true)
3237 this.log('I am not visible');
3242 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3243 if(this.debug === true)
3244 this.log('timed force update detected - allowing this update');
3246 this.current.force_update_at = 0;
3250 if(this.isAutoRefreshable() === true) {
3251 // allow the first update, even if the page is not visible
3252 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3253 if(NETDATA.options.debug.focus === true || this.debug === true)
3254 this.log('canBeAutoRefreshed(): page does not have focus');
3259 if(this.needsRecreation() === true) {
3260 if(this.debug === true)
3261 this.log('canBeAutoRefreshed(): needs re-creation.');
3266 // options valid only for autoRefresh()
3267 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3268 if(NETDATA.globalPanAndZoom.isActive()) {
3269 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3270 if(this.debug === true)
3271 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3276 if(this.debug === true)
3277 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3283 if(this.selected === true) {
3284 if(this.debug === true)
3285 this.log('canBeAutoRefreshed(): I have a selection in place.');
3290 if(this.paused === true) {
3291 if(this.debug === true)
3292 this.log('canBeAutoRefreshed(): I am paused.');
3297 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3298 if(this.debug === true)
3299 this.log('canBeAutoRefreshed(): It is time to update me.');
3309 this.autoRefresh = function(callback) {
3310 if(this.canBeAutoRefreshed() === true && this.running === false) {
3313 state.running = true;
3314 state.updateChart(function() {
3315 state.running = false;
3317 if(typeof callback !== 'undefined')
3322 if(typeof callback !== 'undefined')
3327 this._defaultsFromDownloadedChart = function(chart) {
3329 this.chart_url = chart.url;
3330 this.data_update_every = chart.update_every * 1000;
3331 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3332 this.tm.last_info_downloaded = Date.now();
3334 if(this.title === null)
3335 this.title = chart.title;
3337 if(this.units === null)
3338 this.units = chart.units;
3341 // fetch the chart description from the netdata server
3342 this.getChart = function(callback) {
3343 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3345 this._defaultsFromDownloadedChart(this.chart);
3346 if(typeof callback === 'function') callback();
3349 this.chart_url = "/api/v1/chart?chart=" + this.id;
3351 if(this.debug === true)
3352 this.log('downloading ' + this.chart_url);
3355 url: this.host + this.chart_url,
3358 xhrFields: { withCredentials: true } // required for the cookie
3360 .done(function(chart) {
3361 chart.url = that.chart_url;
3362 that._defaultsFromDownloadedChart(chart);
3363 NETDATA.chartRegistry.add(that.host, that.id, chart);
3366 NETDATA.error(404, that.chart_url);
3367 error('chart not found on url "' + that.chart_url + '"');
3369 .always(function() {
3370 if(typeof callback === 'function') callback();
3375 // ============================================================================================================
3381 NETDATA.resetAllCharts = function(state) {
3382 // first clear the global selection sync
3383 // to make sure no chart is in selected state
3384 state.globalSelectionSyncStop();
3386 // there are 2 possibilities here
3387 // a. state is the global Pan and Zoom master
3388 // b. state is not the global Pan and Zoom master
3390 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3393 // clear the global Pan and Zoom
3394 // this will also refresh the master
3395 // and unblock any charts currently mirroring the master
3396 NETDATA.globalPanAndZoom.clearMaster();
3398 // if we were not the master, reset our status too
3399 // this is required because most probably the mouse
3400 // is over this chart, blocking it from auto-refreshing
3401 if(master === false && (state.paused === true || state.selected === true))
3405 // get or create a chart state, given a DOM element
3406 NETDATA.chartState = function(element) {
3407 var state = $(element).data('netdata-state-object') || null;
3408 if(state === null) {
3409 state = new chartState(element);
3410 $(element).data('netdata-state-object', state);
3415 // ----------------------------------------------------------------------------------------------------------------
3416 // Library functions
3418 // Load a script without jquery
3419 // This is used to load jquery - after it is loaded, we use jquery
3420 NETDATA._loadjQuery = function(callback) {
3421 if(typeof jQuery === 'undefined') {
3422 if(NETDATA.options.debug.main_loop === true)
3423 console.log('loading ' + NETDATA.jQuery);
3425 var script = document.createElement('script');
3426 script.type = 'text/javascript';
3427 script.async = true;
3428 script.src = NETDATA.jQuery;
3430 // script.onabort = onError;
3431 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3432 if(typeof callback === "function")
3433 script.onload = callback;
3435 var s = document.getElementsByTagName('script')[0];
3436 s.parentNode.insertBefore(script, s);
3438 else if(typeof callback === "function")
3442 NETDATA._loadCSS = function(filename) {
3443 // don't use jQuery here
3444 // styles are loaded before jQuery
3445 // to eliminate showing an unstyled page to the user
3447 var fileref = document.createElement("link");
3448 fileref.setAttribute("rel", "stylesheet");
3449 fileref.setAttribute("type", "text/css");
3450 fileref.setAttribute("href", filename);
3452 if (typeof fileref !== 'undefined')
3453 document.getElementsByTagName("head")[0].appendChild(fileref);
3456 NETDATA.colorHex2Rgb = function(hex) {
3457 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3458 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3459 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3460 return r + r + g + g + b + b;
3463 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3465 r: parseInt(result[1], 16),
3466 g: parseInt(result[2], 16),
3467 b: parseInt(result[3], 16)
3471 NETDATA.colorLuminance = function(hex, lum) {
3472 // validate hex string
3473 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3475 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3479 // convert to decimal and change luminosity
3480 var rgb = "#", c, i;
3481 for (i = 0; i < 3; i++) {
3482 c = parseInt(hex.substr(i*2,2), 16);
3483 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3484 rgb += ("00"+c).substr(c.length);
3490 NETDATA.guid = function() {
3492 return Math.floor((1 + Math.random()) * 0x10000)
3497 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3500 NETDATA.zeropad = function(x) {
3501 if(x > -10 && x < 10) return '0' + x.toString();
3502 else return x.toString();
3505 // user function to signal us the DOM has been
3507 NETDATA.updatedDom = function() {
3508 NETDATA.options.updated_dom = true;
3511 NETDATA.ready = function(callback) {
3512 NETDATA.options.pauseCallback = callback;
3515 NETDATA.pause = function(callback) {
3516 if(NETDATA.options.pause === true)
3519 NETDATA.options.pauseCallback = callback;
3522 NETDATA.unpause = function() {
3523 NETDATA.options.pauseCallback = null;
3524 NETDATA.options.updated_dom = true;
3525 NETDATA.options.pause = false;
3528 // ----------------------------------------------------------------------------------------------------------------
3530 // this is purely sequencial charts refresher
3531 // it is meant to be autonomous
3532 NETDATA.chartRefresherNoParallel = function(index) {
3533 if(NETDATA.options.debug.mail_loop === true)
3534 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3536 if(NETDATA.options.updated_dom === true) {
3537 // the dom has been updated
3538 // get the dom parts again
3539 NETDATA.parseDom(NETDATA.chartRefresher);
3542 if(index >= NETDATA.options.targets.length) {
3543 if(NETDATA.options.debug.main_loop === true)
3544 console.log('waiting to restart main loop...');
3546 NETDATA.options.auto_refresher_fast_weight = 0;
3548 setTimeout(function() {
3549 NETDATA.chartRefresher();
3550 }, NETDATA.options.current.idle_between_loops);
3553 var state = NETDATA.options.targets[index];
3555 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3556 if(NETDATA.options.debug.main_loop === true)
3557 console.log('fast rendering...');
3559 state.autoRefresh(function() {
3560 NETDATA.chartRefresherNoParallel(++index);
3564 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3565 NETDATA.options.auto_refresher_fast_weight = 0;
3567 setTimeout(function() {
3568 state.autoRefresh(function() {
3569 NETDATA.chartRefresherNoParallel(++index);
3571 }, NETDATA.options.current.idle_between_charts);
3576 // this is part of the parallel refresher
3577 // its cause is to refresh sequencially all the charts
3578 // that depend on chart library initialization
3579 // it will call the parallel refresher back
3580 // as soon as it sees a chart that its chart library
3582 NETDATA.chartRefresher_uninitialized = function() {
3583 if(NETDATA.options.updated_dom === true) {
3584 // the dom has been updated
3585 // get the dom parts again
3586 NETDATA.parseDom(NETDATA.chartRefresher);
3590 if(NETDATA.options.sequencial.length === 0)
3591 NETDATA.chartRefresher();
3593 var state = NETDATA.options.sequencial.pop();
3594 if(state.library.initialized === true)
3595 NETDATA.chartRefresher();
3597 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3601 NETDATA.chartRefresherWaitTime = function() {
3602 return NETDATA.options.current.idle_parallel_loops;
3605 // the default refresher
3606 // it will create 2 sets of charts:
3607 // - the ones that can be refreshed in parallel
3608 // - the ones that depend on something else
3609 // the first set will be executed in parallel
3610 // the second will be given to NETDATA.chartRefresher_uninitialized()
3611 NETDATA.chartRefresher = function() {
3612 // console.log('auto-refresher...');
3614 if(NETDATA.options.pause === true) {
3615 // console.log('auto-refresher is paused');
3616 setTimeout(NETDATA.chartRefresher,
3617 NETDATA.chartRefresherWaitTime());
3621 if(typeof NETDATA.options.pauseCallback === 'function') {
3622 // console.log('auto-refresher is calling pauseCallback');
3623 NETDATA.options.pause = true;
3624 NETDATA.options.pauseCallback();
3625 NETDATA.chartRefresher();
3629 if(NETDATA.options.current.parallel_refresher === false) {
3630 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3631 NETDATA.chartRefresherNoParallel(0);
3635 if(NETDATA.options.updated_dom === true) {
3636 // the dom has been updated
3637 // get the dom parts again
3638 // console.log('auto-refresher is calling parseDom()');
3639 NETDATA.parseDom(NETDATA.chartRefresher);
3643 var parallel = new Array();
3644 var targets = NETDATA.options.targets;
3645 var len = targets.length;
3648 state = targets[len];
3649 if(state.isVisible() === false || state.running === true)
3652 if(state.library.initialized === false) {
3653 if(state.library.enabled === true) {
3654 state.library.initialize(NETDATA.chartRefresher);
3658 state.error('chart library "' + state.library_name + '" is not enabled.');
3662 parallel.unshift(state);
3665 if(parallel.length > 0) {
3666 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3667 // this will execute the jobs in parallel
3668 $(parallel).each(function() {
3673 // console.log('auto-refresher nothing to do');
3676 // run the next refresh iteration
3677 setTimeout(NETDATA.chartRefresher,
3678 NETDATA.chartRefresherWaitTime());
3681 NETDATA.parseDom = function(callback) {
3682 NETDATA.options.last_page_scroll = Date.now();
3683 NETDATA.options.updated_dom = false;
3685 var targets = $('div[data-netdata]'); //.filter(':visible');
3687 if(NETDATA.options.debug.main_loop === true)
3688 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3690 NETDATA.options.targets = new Array();
3691 var len = targets.length;
3693 // the initialization will take care of sizing
3694 // and the "loading..." message
3695 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3698 if(typeof callback === 'function') callback();
3701 // this is the main function - where everything starts
3702 NETDATA.start = function() {
3703 // this should be called only once
3705 NETDATA.options.page_is_visible = true;
3707 $(window).blur(function() {
3708 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3709 NETDATA.options.page_is_visible = false;
3710 if(NETDATA.options.debug.focus === true)
3711 console.log('Lost Focus!');
3715 $(window).focus(function() {
3716 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3717 NETDATA.options.page_is_visible = true;
3718 if(NETDATA.options.debug.focus === true)
3719 console.log('Focus restored!');
3723 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3724 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3725 NETDATA.options.page_is_visible = false;
3726 if(NETDATA.options.debug.focus === true)
3727 console.log('Document has no focus!');
3731 // bootstrap tab switching
3732 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3734 // bootstrap modal switching
3735 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3736 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3738 // bootstrap collapse switching
3739 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3740 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3742 NETDATA.parseDom(NETDATA.chartRefresher);
3744 // Alarms initialization
3745 setTimeout(NETDATA.alarms.init, 1000);
3747 // Registry initialization
3748 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3750 if(typeof netdataCallback === 'function')
3754 // ----------------------------------------------------------------------------------------------------------------
3757 NETDATA.peityInitialize = function(callback) {
3758 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3760 url: NETDATA.peity_js,
3763 xhrFields: { withCredentials: true } // required for the cookie
3766 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3769 NETDATA.chartLibraries.peity.enabled = false;
3770 NETDATA.error(100, NETDATA.peity_js);
3772 .always(function() {
3773 if(typeof callback === "function")
3778 NETDATA.chartLibraries.peity.enabled = false;
3779 if(typeof callback === "function")
3784 NETDATA.peityChartUpdate = function(state, data) {
3785 state.peity_instance.innerHTML = data.result;
3787 if(state.peity_options.stroke !== state.chartColors()[0]) {
3788 state.peity_options.stroke = state.chartColors()[0];
3789 if(state.chart.chart_type === 'line')
3790 state.peity_options.fill = NETDATA.themes.current.background;
3792 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3795 $(state.peity_instance).peity('line', state.peity_options);
3799 NETDATA.peityChartCreate = function(state, data) {
3800 state.peity_instance = document.createElement('div');
3801 state.element_chart.appendChild(state.peity_instance);
3803 var self = $(state.element);
3804 state.peity_options = {
3805 stroke: NETDATA.themes.current.foreground,
3806 strokeWidth: self.data('peity-strokewidth') || 1,
3807 width: state.chartWidth(),
3808 height: state.chartHeight(),
3809 fill: NETDATA.themes.current.foreground
3812 NETDATA.peityChartUpdate(state, data);
3816 // ----------------------------------------------------------------------------------------------------------------
3819 NETDATA.sparklineInitialize = function(callback) {
3820 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3822 url: NETDATA.sparkline_js,
3825 xhrFields: { withCredentials: true } // required for the cookie
3828 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3831 NETDATA.chartLibraries.sparkline.enabled = false;
3832 NETDATA.error(100, NETDATA.sparkline_js);
3834 .always(function() {
3835 if(typeof callback === "function")
3840 NETDATA.chartLibraries.sparkline.enabled = false;
3841 if(typeof callback === "function")
3846 NETDATA.sparklineChartUpdate = function(state, data) {
3847 state.sparkline_options.width = state.chartWidth();
3848 state.sparkline_options.height = state.chartHeight();
3850 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3854 NETDATA.sparklineChartCreate = function(state, data) {
3855 var self = $(state.element);
3856 var type = self.data('sparkline-type') || 'line';
3857 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3858 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3859 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3860 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3861 var composite = self.data('sparkline-composite') || undefined;
3862 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3863 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3864 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3865 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3866 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3867 var spotColor = self.data('sparkline-spotcolor') || undefined;
3868 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3869 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3870 var spotRadius = self.data('sparkline-spotradius') || undefined;
3871 var valueSpots = self.data('sparkline-valuespots') || undefined;
3872 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3873 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3874 var lineWidth = self.data('sparkline-linewidth') || undefined;
3875 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3876 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3877 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3878 var xvalues = self.data('sparkline-xvalues') || undefined;
3879 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3880 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3881 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3882 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3883 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3884 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3885 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3886 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3887 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3888 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3889 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3890 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3891 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3892 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3893 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3894 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3895 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3896 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3897 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3898 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3899 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3900 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3902 if(spotColor === 'disable') spotColor='';
3903 if(minSpotColor === 'disable') minSpotColor='';
3904 if(maxSpotColor === 'disable') maxSpotColor='';
3906 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3908 state.sparkline_options = {
3910 lineColor: lineColor,
3911 fillColor: fillColor,
3912 chartRangeMin: chartRangeMin,
3913 chartRangeMax: chartRangeMax,
3914 composite: composite,
3915 enableTagOptions: enableTagOptions,
3916 tagOptionPrefix: tagOptionPrefix,
3917 tagValuesAttribute: tagValuesAttribute,
3918 disableHiddenCheck: disableHiddenCheck,
3919 defaultPixelsPerValue: defaultPixelsPerValue,
3920 spotColor: spotColor,
3921 minSpotColor: minSpotColor,
3922 maxSpotColor: maxSpotColor,
3923 spotRadius: spotRadius,
3924 valueSpots: valueSpots,
3925 highlightSpotColor: highlightSpotColor,
3926 highlightLineColor: highlightLineColor,
3927 lineWidth: lineWidth,
3928 normalRangeMin: normalRangeMin,
3929 normalRangeMax: normalRangeMax,
3930 drawNormalOnTop: drawNormalOnTop,
3932 chartRangeClip: chartRangeClip,
3933 chartRangeMinX: chartRangeMinX,
3934 chartRangeMaxX: chartRangeMaxX,
3935 disableInteraction: disableInteraction,
3936 disableTooltips: disableTooltips,
3937 disableHighlight: disableHighlight,
3938 highlightLighten: highlightLighten,
3939 highlightColor: highlightColor,
3940 tooltipContainer: tooltipContainer,
3941 tooltipClassname: tooltipClassname,
3942 tooltipChartTitle: state.title,
3943 tooltipFormat: tooltipFormat,
3944 tooltipPrefix: tooltipPrefix,
3945 tooltipSuffix: tooltipSuffix,
3946 tooltipSkipNull: tooltipSkipNull,
3947 tooltipValueLookups: tooltipValueLookups,
3948 tooltipFormatFieldlist: tooltipFormatFieldlist,
3949 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3950 numberFormatter: numberFormatter,
3951 numberDigitGroupSep: numberDigitGroupSep,
3952 numberDecimalMark: numberDecimalMark,
3953 numberDigitGroupCount: numberDigitGroupCount,
3954 animatedZooms: animatedZooms,
3955 width: state.chartWidth(),
3956 height: state.chartHeight()
3959 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3963 // ----------------------------------------------------------------------------------------------------------------
3970 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3971 if(after < state.netdata_first)
3972 after = state.netdata_first;
3974 if(before > state.netdata_last)
3975 before = state.netdata_last;
3977 state.setMode('zoom');
3978 state.globalSelectionSyncStop();
3979 state.globalSelectionSyncDelay();
3980 state.dygraph_user_action = true;
3981 state.dygraph_force_zoom = true;
3982 state.updateChartPanOrZoom(after, before);
3983 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3986 NETDATA.dygraphSetSelection = function(state, t) {
3987 if(typeof state.dygraph_instance !== 'undefined') {
3988 var r = state.calculateRowForTime(t);
3990 state.dygraph_instance.setSelection(r);
3992 state.dygraph_instance.clearSelection();
3993 state.legendShowUndefined();
4000 NETDATA.dygraphClearSelection = function(state, t) {
4001 if(typeof state.dygraph_instance !== 'undefined') {
4002 state.dygraph_instance.clearSelection();
4007 NETDATA.dygraphSmoothInitialize = function(callback) {
4009 url: NETDATA.dygraph_smooth_js,
4012 xhrFields: { withCredentials: true } // required for the cookie
4015 NETDATA.dygraph.smooth = true;
4016 smoothPlotter.smoothing = 0.3;
4019 NETDATA.dygraph.smooth = false;
4021 .always(function() {
4022 if(typeof callback === "function")
4027 NETDATA.dygraphInitialize = function(callback) {
4028 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4030 url: NETDATA.dygraph_js,
4033 xhrFields: { withCredentials: true } // required for the cookie
4036 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4039 NETDATA.chartLibraries.dygraph.enabled = false;
4040 NETDATA.error(100, NETDATA.dygraph_js);
4042 .always(function() {
4043 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4044 NETDATA.dygraphSmoothInitialize(callback);
4045 else if(typeof callback === "function")
4050 NETDATA.chartLibraries.dygraph.enabled = false;
4051 if(typeof callback === "function")
4056 NETDATA.dygraphChartUpdate = function(state, data) {
4057 var dygraph = state.dygraph_instance;
4059 if(typeof dygraph === 'undefined')
4060 return NETDATA.dygraphChartCreate(state, data);
4062 // when the chart is not visible, and hidden
4063 // if there is a window resize, dygraph detects
4064 // its element size as 0x0.
4065 // this will make it re-appear properly
4067 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4071 file: data.result.data,
4072 colors: state.chartColors(),
4073 labels: data.result.labels,
4074 labelsDivWidth: state.chartWidth() - 70,
4075 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4078 if(state.dygraph_force_zoom === true) {
4079 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4080 state.log('dygraphChartUpdate() forced zoom update');
4082 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4083 options.isZoomedIgnoreProgrammaticZoom = true;
4084 state.dygraph_force_zoom = false;
4086 else if(state.current.name !== 'auto') {
4087 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4088 state.log('dygraphChartUpdate() loose update');
4091 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4092 state.log('dygraphChartUpdate() strict update');
4094 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4095 options.isZoomedIgnoreProgrammaticZoom = true;
4098 options.valueRange = state.dygraph_options.valueRange;
4100 var oldMax = null, oldMin = null;
4101 if(state.__commonMin !== null) {
4102 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4103 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4105 if(state.__commonMax !== null) {
4106 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4107 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4110 if(state.dygraph_smooth_eligible === true) {
4111 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4112 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4113 NETDATA.dygraphChartCreate(state, data);
4118 dygraph.updateOptions(options);
4121 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4122 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4123 options.valueRange[0] = NETDATA.commonMin.get(state);
4126 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4127 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4128 options.valueRange[1] = NETDATA.commonMax.get(state);
4132 if(redraw === true) {
4133 // state.log('forcing redraw to adapt to common- min/max');
4134 dygraph.updateOptions(options);
4137 state.dygraph_last_rendered = Date.now();
4141 NETDATA.dygraphChartCreate = function(state, data) {
4142 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4143 state.log('dygraphChartCreate()');
4145 var self = $(state.element);
4147 var chart_type = state.chart.chart_type;
4148 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4149 chart_type = self.data('dygraph-type') || chart_type;
4151 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4152 smooth = self.data('dygraph-smooth') || smooth;
4154 if(NETDATA.dygraph.smooth === false)
4157 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4158 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4160 state.dygraph_options = {
4161 colors: self.data('dygraph-colors') || state.chartColors(),
4163 // leave a few pixels empty on the right of the chart
4164 rightGap: self.data('dygraph-rightgap') || 5,
4165 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4166 showRoller: self.data('dygraph-showroller') || false,
4168 title: self.data('dygraph-title') || state.title,
4169 titleHeight: self.data('dygraph-titleheight') || 19,
4171 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4172 labels: data.result.labels,
4173 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4174 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4175 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4176 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4177 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4180 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4181 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4183 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4184 xRangePad: self.data('dygraph-xrangepad') || 0,
4185 yRangePad: self.data('dygraph-yrangepad') || 1,
4187 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4189 ylabel: state.units,
4190 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4192 // the function to plot the chart
4195 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4196 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4197 strokePattern: self.data('dygraph-strokepattern') || undefined,
4199 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4200 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4201 drawPoints: self.data('dygraph-drawpoints') || false,
4203 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4204 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4206 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4207 pointSize: self.data('dygraph-pointsize') || 1,
4209 // enabling this makes the chart with little square lines
4210 stepPlot: self.data('dygraph-stepplot') || false,
4212 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4213 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4214 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4216 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4217 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4218 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4219 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4221 drawAxis: self.data('dygraph-drawaxis') || true,
4222 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4223 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4224 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4226 drawGrid: self.data('dygraph-drawgrid') || true,
4227 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4228 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4229 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4231 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4232 sigFigs: self.data('dygraph-sigfigs') || null,
4233 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4234 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4236 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4237 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4238 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4240 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4241 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4245 ticker: Dygraph.dateTicker,
4246 axisLabelFormatter: function (d, gran) {
4247 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4249 valueFormatter: function (ms) {
4250 //var d = new Date(ms);
4251 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4253 // no need to return anything here
4260 valueFormatter: function (x) {
4261 // we format legends with the state object
4262 // no need to do anything here
4263 // return (Math.round(x*100) / 100).toLocaleString();
4264 // return state.legendFormatValue(x);
4269 legendFormatter: function(data) {
4270 var elements = state.element_legend_childs;
4272 // if the hidden div is not there
4273 // we are not managing the legend
4274 if(elements.hidden === null) return;
4276 if (typeof data.x !== 'undefined') {
4277 state.legendSetDate(data.x);
4278 var i = data.series.length;
4280 var series = data.series[i];
4281 if(series.isVisible === true)
4282 state.legendSetLabelValue(series.label, series.y);
4288 drawCallback: function(dygraph, is_initial) {
4289 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4290 state.dygraph_user_action = false;
4292 var x_range = dygraph.xAxisRange();
4293 var after = Math.round(x_range[0]);
4294 var before = Math.round(x_range[1]);
4296 if(NETDATA.options.debug.dygraph === true)
4297 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4299 if(before <= state.netdata_last && after >= state.netdata_first)
4300 state.updateChartPanOrZoom(after, before);
4303 zoomCallback: function(minDate, maxDate, yRanges) {
4304 if(NETDATA.options.debug.dygraph === true)
4305 state.log('dygraphZoomCallback()');
4307 state.globalSelectionSyncStop();
4308 state.globalSelectionSyncDelay();
4309 state.setMode('zoom');
4311 // refresh it to the greatest possible zoom level
4312 state.dygraph_user_action = true;
4313 state.dygraph_force_zoom = true;
4314 state.updateChartPanOrZoom(minDate, maxDate);
4316 highlightCallback: function(event, x, points, row, seriesName) {
4317 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4318 state.log('dygraphHighlightCallback()');
4322 // there is a bug in dygraph when the chart is zoomed enough
4323 // the time it thinks is selected is wrong
4324 // here we calculate the time t based on the row number selected
4326 var t = state.data_after + row * state.data_update_every;
4327 // 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);
4329 state.globalSelectionSync(x);
4331 // fix legend zIndex using the internal structures of dygraph legend module
4332 // this works, but it is a hack!
4333 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4335 unhighlightCallback: function(event) {
4336 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4337 state.log('dygraphUnhighlightCallback()');
4339 state.unpauseChart();
4340 state.globalSelectionSyncStop();
4342 interactionModel : {
4343 mousedown: function(event, dygraph, context) {
4344 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4345 state.log('interactionModel.mousedown()');
4347 state.dygraph_user_action = true;
4348 state.globalSelectionSyncStop();
4350 if(NETDATA.options.debug.dygraph === true)
4351 state.log('dygraphMouseDown()');
4353 // Right-click should not initiate a zoom.
4354 if(event.button && event.button === 2) return;
4356 context.initializeMouseDown(event, dygraph, context);
4358 if(event.button && event.button === 1) {
4359 if (event.altKey || event.shiftKey) {
4360 state.setMode('pan');
4361 state.globalSelectionSyncDelay();
4362 Dygraph.startPan(event, dygraph, context);
4365 state.setMode('zoom');
4366 state.globalSelectionSyncDelay();
4367 Dygraph.startZoom(event, dygraph, context);
4371 if (event.altKey || event.shiftKey) {
4372 state.setMode('zoom');
4373 state.globalSelectionSyncDelay();
4374 Dygraph.startZoom(event, dygraph, context);
4377 state.setMode('pan');
4378 state.globalSelectionSyncDelay();
4379 Dygraph.startPan(event, dygraph, context);
4383 mousemove: function(event, dygraph, context) {
4384 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4385 state.log('interactionModel.mousemove()');
4387 if(context.isPanning) {
4388 state.dygraph_user_action = true;
4389 state.globalSelectionSyncStop();
4390 state.globalSelectionSyncDelay();
4391 state.setMode('pan');
4392 context.is2DPan = false;
4393 Dygraph.movePan(event, dygraph, context);
4395 else if(context.isZooming) {
4396 state.dygraph_user_action = true;
4397 state.globalSelectionSyncStop();
4398 state.globalSelectionSyncDelay();
4399 state.setMode('zoom');
4400 Dygraph.moveZoom(event, dygraph, context);
4403 mouseup: function(event, dygraph, context) {
4404 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4405 state.log('interactionModel.mouseup()');
4407 if (context.isPanning) {
4408 state.dygraph_user_action = true;
4409 state.globalSelectionSyncDelay();
4410 Dygraph.endPan(event, dygraph, context);
4412 else if (context.isZooming) {
4413 state.dygraph_user_action = true;
4414 state.globalSelectionSyncDelay();
4415 Dygraph.endZoom(event, dygraph, context);
4418 click: function(event, dygraph, context) {
4419 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4420 state.log('interactionModel.click()');
4422 event.preventDefault();
4424 dblclick: function(event, dygraph, context) {
4425 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4426 state.log('interactionModel.dblclick()');
4427 NETDATA.resetAllCharts(state);
4429 wheel: function(event, dygraph, context) {
4430 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4431 state.log('interactionModel.wheel()');
4433 // Take the offset of a mouse event on the dygraph canvas and
4434 // convert it to a pair of percentages from the bottom left.
4435 // (Not top left, bottom is where the lower value is.)
4436 function offsetToPercentage(g, offsetX, offsetY) {
4437 // This is calculating the pixel offset of the leftmost date.
4438 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4439 var yar0 = g.yAxisRange(0);
4441 // This is calculating the pixel of the higest value. (Top pixel)
4442 var yOffset = g.toDomCoords(null, yar0[1])[1];
4444 // x y w and h are relative to the corner of the drawing area,
4445 // so that the upper corner of the drawing area is (0, 0).
4446 var x = offsetX - xOffset;
4447 var y = offsetY - yOffset;
4449 // This is computing the rightmost pixel, effectively defining the
4451 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4453 // This is computing the lowest pixel, effectively defining the height.
4454 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4456 // Percentage from the left.
4457 var xPct = w === 0 ? 0 : (x / w);
4458 // Percentage from the top.
4459 var yPct = h === 0 ? 0 : (y / h);
4461 // The (1-) part below changes it from "% distance down from the top"
4462 // to "% distance up from the bottom".
4463 return [xPct, (1-yPct)];
4466 // Adjusts [x, y] toward each other by zoomInPercentage%
4467 // Split it so the left/bottom axis gets xBias/yBias of that change and
4468 // tight/top gets (1-xBias)/(1-yBias) of that change.
4470 // If a bias is missing it splits it down the middle.
4471 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4472 xBias = xBias || 0.5;
4473 yBias = yBias || 0.5;
4475 function adjustAxis(axis, zoomInPercentage, bias) {
4476 var delta = axis[1] - axis[0];
4477 var increment = delta * zoomInPercentage;
4478 var foo = [increment * bias, increment * (1-bias)];
4480 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4483 var yAxes = g.yAxisRanges();
4485 for (var i = 0; i < yAxes.length; i++) {
4486 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4489 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4492 if(event.altKey || event.shiftKey) {
4493 state.dygraph_user_action = true;
4495 state.globalSelectionSyncStop();
4496 state.globalSelectionSyncDelay();
4498 // http://dygraphs.com/gallery/interaction-api.js
4500 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4502 normal_def = event.wheelDelta / 40;
4505 normal_def = event.deltaY * -1.2;
4507 var normal = (event.detail) ? event.detail * -1 : normal_def;
4508 var percentage = normal / 50;
4510 if (!(event.offsetX && event.offsetY)){
4511 event.offsetX = event.layerX - event.target.offsetLeft;
4512 event.offsetY = event.layerY - event.target.offsetTop;
4515 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4516 var xPct = percentages[0];
4517 var yPct = percentages[1];
4519 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4520 var after = new_x_range[0];
4521 var before = new_x_range[1];
4523 var first = state.netdata_first + state.data_update_every;
4524 var last = state.netdata_last + state.data_update_every;
4527 after -= (before - last);
4534 state.setMode('zoom');
4535 if(state.updateChartPanOrZoom(after, before) === true)
4536 dygraph.updateOptions({ dateWindow: [ after, before ] });
4538 event.preventDefault();
4541 touchstart: function(event, dygraph, context) {
4542 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4543 state.log('interactionModel.touchstart()');
4545 state.dygraph_user_action = true;
4546 state.setMode('zoom');
4549 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4551 // we overwrite the touch directions at the end, to overwrite
4552 // the internal default of dygraphs
4553 context.touchDirections = { x: true, y: false };
4555 state.dygraph_last_touch_start = Date.now();
4556 state.dygraph_last_touch_move = 0;
4558 if(typeof event.touches[0].pageX === 'number')
4559 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4561 state.dygraph_last_touch_page_x = 0;
4563 touchmove: function(event, dygraph, context) {
4564 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4565 state.log('interactionModel.touchmove()');
4567 state.dygraph_user_action = true;
4568 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4570 state.dygraph_last_touch_move = Date.now();
4572 touchend: function(event, dygraph, context) {
4573 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4574 state.log('interactionModel.touchend()');
4576 state.dygraph_user_action = true;
4577 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4579 // if it didn't move, it is a selection
4580 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4581 // internal api of dygraphs
4582 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4583 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4584 if(NETDATA.dygraphSetSelection(state, t) === true)
4585 state.globalSelectionSync(t);
4588 // if it was double tap within double click time, reset the charts
4589 var now = Date.now();
4590 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4591 if(state.dygraph_last_touch_move === 0) {
4592 var dt = now - state.dygraph_last_touch_end;
4593 if(dt <= NETDATA.options.current.double_click_speed)
4594 NETDATA.resetAllCharts(state);
4598 // remember the timestamp of the last touch end
4599 state.dygraph_last_touch_end = now;
4604 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4605 state.dygraph_options.drawGrid = false;
4606 state.dygraph_options.drawAxis = false;
4607 state.dygraph_options.title = undefined;
4608 state.dygraph_options.ylabel = undefined;
4609 state.dygraph_options.yLabelWidth = 0;
4610 state.dygraph_options.labelsDivWidth = 120;
4611 state.dygraph_options.labelsDivStyles.width = '120px';
4612 state.dygraph_options.labelsSeparateLines = true;
4613 state.dygraph_options.rightGap = 0;
4614 state.dygraph_options.yRangePad = 1;
4617 if(smooth === true) {
4618 state.dygraph_smooth_eligible = true;
4620 if(NETDATA.options.current.smooth_plot === true)
4621 state.dygraph_options.plotter = smoothPlotter;
4623 else state.dygraph_smooth_eligible = false;
4625 state.dygraph_instance = new Dygraph(state.element_chart,
4626 data.result.data, state.dygraph_options);
4628 state.dygraph_force_zoom = false;
4629 state.dygraph_user_action = false;
4630 state.dygraph_last_rendered = Date.now();
4632 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4633 state.__commonMin = self.data('common-min') || null;
4634 state.__commonMax = self.data('common-max') || null;
4637 state.log('incompatible version of dygraphs detected');
4638 state.__commonMin = null;
4639 state.__commonMax = null;
4645 // ----------------------------------------------------------------------------------------------------------------
4648 NETDATA.morrisInitialize = function(callback) {
4649 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4651 // morris requires raphael
4652 if(!NETDATA.chartLibraries.raphael.initialized) {
4653 if(NETDATA.chartLibraries.raphael.enabled) {
4654 NETDATA.raphaelInitialize(function() {
4655 NETDATA.morrisInitialize(callback);
4659 NETDATA.chartLibraries.morris.enabled = false;
4660 if(typeof callback === "function")
4665 NETDATA._loadCSS(NETDATA.morris_css);
4668 url: NETDATA.morris_js,
4671 xhrFields: { withCredentials: true } // required for the cookie
4674 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4677 NETDATA.chartLibraries.morris.enabled = false;
4678 NETDATA.error(100, NETDATA.morris_js);
4680 .always(function() {
4681 if(typeof callback === "function")
4687 NETDATA.chartLibraries.morris.enabled = false;
4688 if(typeof callback === "function")
4693 NETDATA.morrisChartUpdate = function(state, data) {
4694 state.morris_instance.setData(data.result.data);
4698 NETDATA.morrisChartCreate = function(state, data) {
4700 state.morris_options = {
4701 element: state.element_chart.id,
4702 data: data.result.data,
4704 ykeys: data.dimension_names,
4705 labels: data.dimension_names,
4711 continuousLine: false,
4712 behaveLikeLine: false
4715 if(state.chart.chart_type === 'line')
4716 state.morris_instance = new Morris.Line(state.morris_options);
4718 else if(state.chart.chart_type === 'area') {
4719 state.morris_options.behaveLikeLine = true;
4720 state.morris_instance = new Morris.Area(state.morris_options);
4723 state.morris_instance = new Morris.Area(state.morris_options);
4728 // ----------------------------------------------------------------------------------------------------------------
4731 NETDATA.raphaelInitialize = function(callback) {
4732 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4734 url: NETDATA.raphael_js,
4737 xhrFields: { withCredentials: true } // required for the cookie
4740 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4743 NETDATA.chartLibraries.raphael.enabled = false;
4744 NETDATA.error(100, NETDATA.raphael_js);
4746 .always(function() {
4747 if(typeof callback === "function")
4752 NETDATA.chartLibraries.raphael.enabled = false;
4753 if(typeof callback === "function")
4758 NETDATA.raphaelChartUpdate = function(state, data) {
4759 $(state.element_chart).raphael(data.result, {
4760 width: state.chartWidth(),
4761 height: state.chartHeight()
4767 NETDATA.raphaelChartCreate = function(state, data) {
4768 $(state.element_chart).raphael(data.result, {
4769 width: state.chartWidth(),
4770 height: state.chartHeight()
4776 // ----------------------------------------------------------------------------------------------------------------
4779 NETDATA.c3Initialize = function(callback) {
4780 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4783 if(!NETDATA.chartLibraries.d3.initialized) {
4784 if(NETDATA.chartLibraries.d3.enabled) {
4785 NETDATA.d3Initialize(function() {
4786 NETDATA.c3Initialize(callback);
4790 NETDATA.chartLibraries.c3.enabled = false;
4791 if(typeof callback === "function")
4796 NETDATA._loadCSS(NETDATA.c3_css);
4802 xhrFields: { withCredentials: true } // required for the cookie
4805 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4808 NETDATA.chartLibraries.c3.enabled = false;
4809 NETDATA.error(100, NETDATA.c3_js);
4811 .always(function() {
4812 if(typeof callback === "function")
4818 NETDATA.chartLibraries.c3.enabled = false;
4819 if(typeof callback === "function")
4824 NETDATA.c3ChartUpdate = function(state, data) {
4825 state.c3_instance.destroy();
4826 return NETDATA.c3ChartCreate(state, data);
4828 //state.c3_instance.load({
4829 // rows: data.result,
4836 NETDATA.c3ChartCreate = function(state, data) {
4838 state.element_chart.id = 'c3-' + state.uuid;
4839 // console.log('id = ' + state.element_chart.id);
4841 state.c3_instance = c3.generate({
4842 bindto: '#' + state.element_chart.id,
4844 width: state.chartWidth(),
4845 height: state.chartHeight()
4848 pattern: state.chartColors()
4853 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4859 format: function(x) {
4860 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4887 // console.log(state.c3_instance);
4892 // ----------------------------------------------------------------------------------------------------------------
4895 NETDATA.d3Initialize = function(callback) {
4896 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4901 xhrFields: { withCredentials: true } // required for the cookie
4904 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4907 NETDATA.chartLibraries.d3.enabled = false;
4908 NETDATA.error(100, NETDATA.d3_js);
4910 .always(function() {
4911 if(typeof callback === "function")
4916 NETDATA.chartLibraries.d3.enabled = false;
4917 if(typeof callback === "function")
4922 NETDATA.d3ChartUpdate = function(state, data) {
4926 NETDATA.d3ChartCreate = function(state, data) {
4930 // ----------------------------------------------------------------------------------------------------------------
4933 NETDATA.googleInitialize = function(callback) {
4934 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4936 url: NETDATA.google_js,
4939 xhrFields: { withCredentials: true } // required for the cookie
4942 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4943 google.load('visualization', '1.1', {
4944 'packages': ['corechart', 'controls'],
4945 'callback': callback
4949 NETDATA.chartLibraries.google.enabled = false;
4950 NETDATA.error(100, NETDATA.google_js);
4951 if(typeof callback === "function")
4956 NETDATA.chartLibraries.google.enabled = false;
4957 if(typeof callback === "function")
4962 NETDATA.googleChartUpdate = function(state, data) {
4963 var datatable = new google.visualization.DataTable(data.result);
4964 state.google_instance.draw(datatable, state.google_options);
4968 NETDATA.googleChartCreate = function(state, data) {
4969 var datatable = new google.visualization.DataTable(data.result);
4971 state.google_options = {
4972 colors: state.chartColors(),
4974 // do not set width, height - the chart resizes itself
4975 //width: state.chartWidth(),
4976 //height: state.chartHeight(),
4981 // title: "Time of Day",
4982 // format:'HH:mm:ss',
4983 viewWindowMode: 'maximized',
4995 viewWindowMode: 'pretty',
5010 focusTarget: 'category',
5017 titlePosition: 'out',
5028 curveType: 'function',
5033 switch(state.chart.chart_type) {
5035 state.google_options.vAxis.viewWindowMode = 'maximized';
5036 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5037 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5041 state.google_options.isStacked = true;
5042 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5043 state.google_options.vAxis.viewWindowMode = 'maximized';
5044 state.google_options.vAxis.minValue = null;
5045 state.google_options.vAxis.maxValue = null;
5046 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5051 state.google_options.lineWidth = 2;
5052 state.google_instance = new google.visualization.LineChart(state.element_chart);
5056 state.google_instance.draw(datatable, state.google_options);
5060 // ----------------------------------------------------------------------------------------------------------------
5062 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5063 if(typeof value !== 'number') value = 0;
5064 if(typeof min !== 'number') min = 0;
5065 if(typeof max !== 'number') max = 0;
5067 if(min > value) min = value;
5068 if(max < value) max = value;
5070 // make sure it is zero based
5071 if(min > 0) min = 0;
5072 if(max < 0) max = 0;
5077 pcent = Math.round(value * 100 / max);
5078 if(pcent === 0) pcent = 0.1;
5082 pcent = Math.round(-value * 100 / min);
5083 if(pcent === 0) pcent = -0.1;
5089 // ----------------------------------------------------------------------------------------------------------------
5092 NETDATA.easypiechartInitialize = function(callback) {
5093 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5095 url: NETDATA.easypiechart_js,
5098 xhrFields: { withCredentials: true } // required for the cookie
5101 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5104 NETDATA.chartLibraries.easypiechart.enabled = false;
5105 NETDATA.error(100, NETDATA.easypiechart_js);
5107 .always(function() {
5108 if(typeof callback === "function")
5113 NETDATA.chartLibraries.easypiechart.enabled = false;
5114 if(typeof callback === "function")
5119 NETDATA.easypiechartClearSelection = function(state) {
5120 if(typeof state.easyPieChartEvent !== 'undefined') {
5121 if(state.easyPieChartEvent.timer !== null)
5122 clearTimeout(state.easyPieChartEvent.timer);
5124 state.easyPieChartEvent.timer = null;
5127 if(state.isAutoRefreshable() === true && state.data !== null) {
5128 NETDATA.easypiechartChartUpdate(state, state.data);
5131 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5132 state.easyPieChart_instance.update(0);
5134 state.easyPieChart_instance.enableAnimation();
5139 NETDATA.easypiechartSetSelection = function(state, t) {
5140 if(state.timeIsVisible(t) !== true)
5141 return NETDATA.easypiechartClearSelection(state);
5143 var slot = state.calculateRowForTime(t);
5144 if(slot < 0 || slot >= state.data.result.length)
5145 return NETDATA.easypiechartClearSelection(state);
5147 if(typeof state.easyPieChartEvent === 'undefined') {
5148 state.easyPieChartEvent = {
5155 var value = state.data.result[state.data.result.length - 1 - slot];
5156 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5157 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5158 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5160 state.easyPieChartEvent.value = value;
5161 state.easyPieChartEvent.pcent = pcent;
5162 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5164 if(state.easyPieChartEvent.timer === null) {
5165 state.easyPieChart_instance.disableAnimation();
5167 state.easyPieChartEvent.timer = setTimeout(function() {
5168 state.easyPieChartEvent.timer = null;
5169 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5170 }, NETDATA.options.current.charts_selection_animation_delay);
5176 NETDATA.easypiechartChartUpdate = function(state, data) {
5177 var value, min, max, pcent;
5179 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5184 value = data.result[0];
5185 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5186 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5187 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5190 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5191 state.easyPieChart_instance.update(pcent);
5195 NETDATA.easypiechartChartCreate = function(state, data) {
5196 var self = $(state.element);
5197 var chart = $(state.element_chart);
5199 var value = data.result[0];
5200 var min = self.data('easypiechart-min-value') || null;
5201 var max = self.data('easypiechart-max-value') || null;
5202 var adjust = self.data('easypiechart-adjust') || null;
5205 min = NETDATA.commonMin.get(state);
5206 state.easyPieChartMin = null;
5209 state.easyPieChartMin = min;
5212 max = NETDATA.commonMax.get(state);
5213 state.easyPieChartMax = null;
5216 state.easyPieChartMax = max;
5218 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5220 chart.data('data-percent', pcent);
5224 case 'width': size = state.chartHeight(); break;
5225 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5226 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5228 default: size = state.chartWidth(); break;
5230 state.element.style.width = size + 'px';
5231 state.element.style.height = size + 'px';
5233 var stroke = Math.floor(size / 22);
5234 if(stroke < 3) stroke = 2;
5236 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5237 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5238 state.easyPieChartLabel = document.createElement('span');
5239 state.easyPieChartLabel.className = 'easyPieChartLabel';
5240 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5241 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5242 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5243 state.element_chart.appendChild(state.easyPieChartLabel);
5245 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5246 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5247 state.easyPieChartTitle = document.createElement('span');
5248 state.easyPieChartTitle.className = 'easyPieChartTitle';
5249 state.easyPieChartTitle.innerText = state.title;
5250 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5251 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5252 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5253 state.element_chart.appendChild(state.easyPieChartTitle);
5255 var unitfontsize = Math.round(titlefontsize * 0.9);
5256 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5257 state.easyPieChartUnits = document.createElement('span');
5258 state.easyPieChartUnits.className = 'easyPieChartUnits';
5259 state.easyPieChartUnits.innerText = state.units;
5260 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5261 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5262 state.element_chart.appendChild(state.easyPieChartUnits);
5264 var barColor = self.data('easypiechart-barcolor');
5265 if(typeof barColor === 'undefined' || barColor === null)
5266 barColor = state.chartColors()[0];
5268 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5269 var tmp = eval(barColor);
5270 if(typeof tmp === 'function')
5274 chart.easyPieChart({
5276 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5277 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5278 scaleLength: self.data('easypiechart-scalelength') || 5,
5279 lineCap: self.data('easypiechart-linecap') || 'round',
5280 lineWidth: self.data('easypiechart-linewidth') || stroke,
5281 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5282 size: self.data('easypiechart-size') || size,
5283 rotate: self.data('easypiechart-rotate') || 0,
5284 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5285 easing: self.data('easypiechart-easing') || undefined
5288 // when we just re-create the chart
5289 // do not animate the first update
5291 if(typeof state.easyPieChart_instance !== 'undefined')
5294 state.easyPieChart_instance = chart.data('easyPieChart');
5295 if(animate === false) state.easyPieChart_instance.disableAnimation();
5296 state.easyPieChart_instance.update(pcent);
5297 if(animate === false) state.easyPieChart_instance.enableAnimation();
5301 // ----------------------------------------------------------------------------------------------------------------
5304 NETDATA.gaugeInitialize = function(callback) {
5305 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5307 url: NETDATA.gauge_js,
5310 xhrFields: { withCredentials: true } // required for the cookie
5313 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5316 NETDATA.chartLibraries.gauge.enabled = false;
5317 NETDATA.error(100, NETDATA.gauge_js);
5319 .always(function() {
5320 if(typeof callback === "function")
5325 NETDATA.chartLibraries.gauge.enabled = false;
5326 if(typeof callback === "function")
5331 NETDATA.gaugeAnimation = function(state, status) {
5334 if(typeof status === 'boolean' && status === false)
5336 else if(typeof status === 'number')
5339 // console.log('gauge speed ' + speed);
5340 state.gauge_instance.animationSpeed = speed;
5341 state.___gaugeOld__.speed = speed;
5344 NETDATA.gaugeSet = function(state, value, min, max) {
5345 if(typeof value !== 'number') value = 0;
5346 if(typeof min !== 'number') min = 0;
5347 if(typeof max !== 'number') max = 0;
5348 if(value > max) max = value;
5349 if(value < min) min = value;
5358 // gauge.js has an issue if the needle
5359 // is smaller than min or larger than max
5360 // when we set the new values
5361 // the needle will go crazy
5363 // to prevent it, we always feed it
5364 // with a percentage, so that the needle
5365 // is always between min and max
5366 var pcent = (value - min) * 100 / (max - min);
5368 // these should never happen
5369 if(pcent < 0) pcent = 0;
5370 if(pcent > 100) pcent = 100;
5372 state.gauge_instance.set(pcent);
5373 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5375 state.___gaugeOld__.value = value;
5376 state.___gaugeOld__.min = min;
5377 state.___gaugeOld__.max = max;
5380 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5381 if(state.___gaugeOld__.valueLabel !== value) {
5382 state.___gaugeOld__.valueLabel = value;
5383 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5385 if(state.___gaugeOld__.minLabel !== min) {
5386 state.___gaugeOld__.minLabel = min;
5387 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5389 if(state.___gaugeOld__.maxLabel !== max) {
5390 state.___gaugeOld__.maxLabel = max;
5391 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5395 NETDATA.gaugeClearSelection = function(state) {
5396 if(typeof state.gaugeEvent !== 'undefined') {
5397 if(state.gaugeEvent.timer !== null)
5398 clearTimeout(state.gaugeEvent.timer);
5400 state.gaugeEvent.timer = null;
5403 if(state.isAutoRefreshable() === true && state.data !== null) {
5404 NETDATA.gaugeChartUpdate(state, state.data);
5407 NETDATA.gaugeAnimation(state, false);
5408 NETDATA.gaugeSet(state, null, null, null);
5409 NETDATA.gaugeSetLabels(state, null, null, null);
5412 NETDATA.gaugeAnimation(state, true);
5416 NETDATA.gaugeSetSelection = function(state, t) {
5417 if(state.timeIsVisible(t) !== true)
5418 return NETDATA.gaugeClearSelection(state);
5420 var slot = state.calculateRowForTime(t);
5421 if(slot < 0 || slot >= state.data.result.length)
5422 return NETDATA.gaugeClearSelection(state);
5424 if(typeof state.gaugeEvent === 'undefined') {
5425 state.gaugeEvent = {
5433 var value = state.data.result[state.data.result.length - 1 - slot];
5434 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5435 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5437 // make sure it is zero based
5438 if(min > 0) min = 0;
5439 if(max < 0) max = 0;
5441 state.gaugeEvent.value = value;
5442 state.gaugeEvent.min = min;
5443 state.gaugeEvent.max = max;
5444 NETDATA.gaugeSetLabels(state, value, min, max);
5446 if(state.gaugeEvent.timer === null) {
5447 NETDATA.gaugeAnimation(state, false);
5449 state.gaugeEvent.timer = setTimeout(function() {
5450 state.gaugeEvent.timer = null;
5451 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5452 }, NETDATA.options.current.charts_selection_animation_delay);
5458 NETDATA.gaugeChartUpdate = function(state, data) {
5459 var value, min, max;
5461 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5465 NETDATA.gaugeSetLabels(state, null, null, null);
5468 value = data.result[0];
5469 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5470 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5471 if(value < min) min = value;
5472 if(value > max) max = value;
5474 // make sure it is zero based
5475 if(min > 0) min = 0;
5476 if(max < 0) max = 0;
5478 NETDATA.gaugeSetLabels(state, value, min, max);
5481 NETDATA.gaugeSet(state, value, min, max);
5485 NETDATA.gaugeChartCreate = function(state, data) {
5486 var self = $(state.element);
5487 // var chart = $(state.element_chart);
5489 var value = data.result[0];
5490 var min = self.data('gauge-min-value') || null;
5491 var max = self.data('gauge-max-value') || null;
5492 var adjust = self.data('gauge-adjust') || null;
5493 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5494 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5495 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5496 var stopColor = self.data('gauge-stop-color') || void 0;
5497 var generateGradient = self.data('gauge-generate-gradient') || false;
5500 min = NETDATA.commonMin.get(state);
5501 state.gaugeMin = null;
5504 state.gaugeMin = min;
5507 max = NETDATA.commonMax.get(state);
5508 state.gaugeMax = null;
5511 state.gaugeMax = max;
5513 // make sure it is zero based
5514 if(min > 0) min = 0;
5515 if(max < 0) max = 0;
5517 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5519 // case 'width': width = height * ratio; break;
5521 // default: height = width / ratio; break;
5523 //state.element.style.width = width.toString() + 'px';
5524 //state.element.style.height = height.toString() + 'px';
5529 lines: 12, // The number of lines to draw
5530 angle: 0.15, // The length of each line
5531 lineWidth: 0.44, // 0.44 The line thickness
5533 length: 0.8, // 0.9 The radius of the inner circle
5534 strokeWidth: 0.035, // The rotation offset
5535 color: pointerColor // Fill color
5537 colorStart: startColor, // Colors
5538 colorStop: stopColor, // just experiment with them
5539 strokeColor: strokeColor, // to see which ones work best for you
5541 generateGradient: (generateGradient === true)?true:false,
5545 if (generateGradient.constructor === Array) {
5547 // data-gauge-generate-gradient="[0, 50, 100]"
5548 // data-gauge-gradient-percent-color-0="#FFFFFF"
5549 // data-gauge-gradient-percent-color-50="#999900"
5550 // data-gauge-gradient-percent-color-100="#000000"
5552 options.percentColors = new Array();
5553 var len = generateGradient.length;
5555 var pcent = generateGradient[len];
5556 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5557 if(color !== false) {
5558 var a = new Array();
5561 options.percentColors.unshift(a);
5564 if(options.percentColors.length === 0)
5565 delete options.percentColors;
5567 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5568 options.percentColors = [
5569 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5570 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5571 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5572 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5573 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5574 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5575 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5576 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5577 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5578 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5579 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5582 state.gauge_canvas = document.createElement('canvas');
5583 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5584 state.gauge_canvas.className = 'gaugeChart';
5585 state.gauge_canvas.width = width;
5586 state.gauge_canvas.height = height;
5587 state.element_chart.appendChild(state.gauge_canvas);
5589 var valuefontsize = Math.floor(height / 6);
5590 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5591 state.gaugeChartLabel = document.createElement('span');
5592 state.gaugeChartLabel.className = 'gaugeChartLabel';
5593 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5594 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5595 state.element_chart.appendChild(state.gaugeChartLabel);
5597 var titlefontsize = Math.round(valuefontsize / 2);
5599 state.gaugeChartTitle = document.createElement('span');
5600 state.gaugeChartTitle.className = 'gaugeChartTitle';
5601 state.gaugeChartTitle.innerText = state.title;
5602 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5603 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5604 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5605 state.element_chart.appendChild(state.gaugeChartTitle);
5607 var unitfontsize = Math.round(titlefontsize * 0.9);
5608 state.gaugeChartUnits = document.createElement('span');
5609 state.gaugeChartUnits.className = 'gaugeChartUnits';
5610 state.gaugeChartUnits.innerText = state.units;
5611 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5612 state.element_chart.appendChild(state.gaugeChartUnits);
5614 state.gaugeChartMin = document.createElement('span');
5615 state.gaugeChartMin.className = 'gaugeChartMin';
5616 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5617 state.element_chart.appendChild(state.gaugeChartMin);
5619 state.gaugeChartMax = document.createElement('span');
5620 state.gaugeChartMax.className = 'gaugeChartMax';
5621 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5622 state.element_chart.appendChild(state.gaugeChartMax);
5624 // when we just re-create the chart
5625 // do not animate the first update
5627 if(typeof state.gauge_instance !== 'undefined')
5630 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5632 state.___gaugeOld__ = {
5641 // we will always feed a percentage
5642 state.gauge_instance.minValue = 0;
5643 state.gauge_instance.maxValue = 100;
5645 NETDATA.gaugeAnimation(state, animate);
5646 NETDATA.gaugeSet(state, value, min, max);
5647 NETDATA.gaugeSetLabels(state, value, min, max);
5648 NETDATA.gaugeAnimation(state, true);
5652 // ----------------------------------------------------------------------------------------------------------------
5653 // Charts Libraries Registration
5655 NETDATA.chartLibraries = {
5657 initialize: NETDATA.dygraphInitialize,
5658 create: NETDATA.dygraphChartCreate,
5659 update: NETDATA.dygraphChartUpdate,
5660 resize: function(state) {
5661 if(typeof state.dygraph_instance.resize === 'function')
5662 state.dygraph_instance.resize();
5664 setSelection: NETDATA.dygraphSetSelection,
5665 clearSelection: NETDATA.dygraphClearSelection,
5666 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5669 format: function(state) { return 'json'; },
5670 options: function(state) { return 'ms|flip'; },
5671 legend: function(state) {
5672 if(this.isSparkline(state) === false)
5673 return 'right-side';
5677 autoresize: function(state) { return true; },
5678 max_updates_to_recreate: function(state) { return 5000; },
5679 track_colors: function(state) { return true; },
5680 pixels_per_point: function(state) {
5681 if(this.isSparkline(state) === false)
5687 isSparkline: function(state) {
5688 if(typeof state.dygraph_sparkline === 'undefined') {
5689 var t = $(state.element).data('dygraph-theme');
5690 if(t === 'sparkline')
5691 state.dygraph_sparkline = true;
5693 state.dygraph_sparkline = false;
5695 return state.dygraph_sparkline;
5699 initialize: NETDATA.sparklineInitialize,
5700 create: NETDATA.sparklineChartCreate,
5701 update: NETDATA.sparklineChartUpdate,
5703 setSelection: undefined, // function(state, t) { return true; },
5704 clearSelection: undefined, // function(state) { return true; },
5705 toolboxPanAndZoom: null,
5708 format: function(state) { return 'array'; },
5709 options: function(state) { return 'flip|abs'; },
5710 legend: function(state) { return null; },
5711 autoresize: function(state) { return false; },
5712 max_updates_to_recreate: function(state) { return 5000; },
5713 track_colors: function(state) { return false; },
5714 pixels_per_point: function(state) { return 3; }
5717 initialize: NETDATA.peityInitialize,
5718 create: NETDATA.peityChartCreate,
5719 update: NETDATA.peityChartUpdate,
5721 setSelection: undefined, // function(state, t) { return true; },
5722 clearSelection: undefined, // function(state) { return true; },
5723 toolboxPanAndZoom: null,
5726 format: function(state) { return 'ssvcomma'; },
5727 options: function(state) { return 'null2zero|flip|abs'; },
5728 legend: function(state) { return null; },
5729 autoresize: function(state) { return false; },
5730 max_updates_to_recreate: function(state) { return 5000; },
5731 track_colors: function(state) { return false; },
5732 pixels_per_point: function(state) { return 3; }
5735 initialize: NETDATA.morrisInitialize,
5736 create: NETDATA.morrisChartCreate,
5737 update: NETDATA.morrisChartUpdate,
5739 setSelection: undefined, // function(state, t) { return true; },
5740 clearSelection: undefined, // function(state) { return true; },
5741 toolboxPanAndZoom: null,
5744 format: function(state) { return 'json'; },
5745 options: function(state) { return 'objectrows|ms'; },
5746 legend: function(state) { return null; },
5747 autoresize: function(state) { return false; },
5748 max_updates_to_recreate: function(state) { return 50; },
5749 track_colors: function(state) { return false; },
5750 pixels_per_point: function(state) { return 15; }
5753 initialize: NETDATA.googleInitialize,
5754 create: NETDATA.googleChartCreate,
5755 update: NETDATA.googleChartUpdate,
5757 setSelection: undefined, //function(state, t) { return true; },
5758 clearSelection: undefined, //function(state) { return true; },
5759 toolboxPanAndZoom: null,
5762 format: function(state) { return 'datatable'; },
5763 options: function(state) { return ''; },
5764 legend: function(state) { return null; },
5765 autoresize: function(state) { return false; },
5766 max_updates_to_recreate: function(state) { return 300; },
5767 track_colors: function(state) { return false; },
5768 pixels_per_point: function(state) { return 4; }
5771 initialize: NETDATA.raphaelInitialize,
5772 create: NETDATA.raphaelChartCreate,
5773 update: NETDATA.raphaelChartUpdate,
5775 setSelection: undefined, // function(state, t) { return true; },
5776 clearSelection: undefined, // function(state) { return true; },
5777 toolboxPanAndZoom: null,
5780 format: function(state) { return 'json'; },
5781 options: function(state) { return ''; },
5782 legend: function(state) { return null; },
5783 autoresize: function(state) { return false; },
5784 max_updates_to_recreate: function(state) { return 5000; },
5785 track_colors: function(state) { return false; },
5786 pixels_per_point: function(state) { return 3; }
5789 initialize: NETDATA.c3Initialize,
5790 create: NETDATA.c3ChartCreate,
5791 update: NETDATA.c3ChartUpdate,
5793 setSelection: undefined, // function(state, t) { return true; },
5794 clearSelection: undefined, // function(state) { return true; },
5795 toolboxPanAndZoom: null,
5798 format: function(state) { return 'csvjsonarray'; },
5799 options: function(state) { return 'milliseconds'; },
5800 legend: function(state) { return null; },
5801 autoresize: function(state) { return false; },
5802 max_updates_to_recreate: function(state) { return 5000; },
5803 track_colors: function(state) { return false; },
5804 pixels_per_point: function(state) { return 15; }
5807 initialize: NETDATA.d3Initialize,
5808 create: NETDATA.d3ChartCreate,
5809 update: NETDATA.d3ChartUpdate,
5811 setSelection: undefined, // function(state, t) { return true; },
5812 clearSelection: undefined, // function(state) { return true; },
5813 toolboxPanAndZoom: null,
5816 format: function(state) { return 'json'; },
5817 options: function(state) { return ''; },
5818 legend: function(state) { return null; },
5819 autoresize: function(state) { return false; },
5820 max_updates_to_recreate: function(state) { return 5000; },
5821 track_colors: function(state) { return false; },
5822 pixels_per_point: function(state) { return 3; }
5825 initialize: NETDATA.easypiechartInitialize,
5826 create: NETDATA.easypiechartChartCreate,
5827 update: NETDATA.easypiechartChartUpdate,
5829 setSelection: NETDATA.easypiechartSetSelection,
5830 clearSelection: NETDATA.easypiechartClearSelection,
5831 toolboxPanAndZoom: null,
5834 format: function(state) { return 'array'; },
5835 options: function(state) { return 'absolute'; },
5836 legend: function(state) { return null; },
5837 autoresize: function(state) { return false; },
5838 max_updates_to_recreate: function(state) { return 5000; },
5839 track_colors: function(state) { return true; },
5840 pixels_per_point: function(state) { return 3; },
5844 initialize: NETDATA.gaugeInitialize,
5845 create: NETDATA.gaugeChartCreate,
5846 update: NETDATA.gaugeChartUpdate,
5848 setSelection: NETDATA.gaugeSetSelection,
5849 clearSelection: NETDATA.gaugeClearSelection,
5850 toolboxPanAndZoom: null,
5853 format: function(state) { return 'array'; },
5854 options: function(state) { return 'absolute'; },
5855 legend: function(state) { return null; },
5856 autoresize: function(state) { return false; },
5857 max_updates_to_recreate: function(state) { return 5000; },
5858 track_colors: function(state) { return true; },
5859 pixels_per_point: function(state) { return 3; },
5864 NETDATA.registerChartLibrary = function(library, url) {
5865 if(NETDATA.options.debug.libraries === true)
5866 console.log("registering chart library: " + library);
5868 NETDATA.chartLibraries[library].url = url;
5869 NETDATA.chartLibraries[library].initialized = true;
5870 NETDATA.chartLibraries[library].enabled = true;
5873 // ----------------------------------------------------------------------------------------------------------------
5874 // Load required JS libraries and CSS
5876 NETDATA.requiredJs = [
5878 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5880 isAlreadyLoaded: function() {
5881 // check if bootstrap is loaded
5882 if(typeof $().emulateTransitionEnd == 'function')
5885 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5893 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5894 isAlreadyLoaded: function() { return false; }
5898 NETDATA.requiredCSS = [
5900 url: NETDATA.themes.current.bootstrap_css,
5901 isAlreadyLoaded: function() {
5902 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5909 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5910 isAlreadyLoaded: function() { return false; }
5913 url: NETDATA.themes.current.dashboard_css,
5914 isAlreadyLoaded: function() { return false; }
5918 NETDATA.loadedRequiredJs = 0;
5919 NETDATA.loadRequiredJs = function(index, callback) {
5920 if(index >= NETDATA.requiredJs.length) {
5921 if(typeof callback === 'function')
5926 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5927 NETDATA.loadedRequiredJs++;
5928 NETDATA.loadRequiredJs(++index, callback);
5932 if(NETDATA.options.debug.main_loop === true)
5933 console.log('loading ' + NETDATA.requiredJs[index].url);
5936 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5940 url: NETDATA.requiredJs[index].url,
5943 xhrFields: { withCredentials: true } // required for the cookie
5946 if(NETDATA.options.debug.main_loop === true)
5947 console.log('loaded ' + NETDATA.requiredJs[index].url);
5950 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5952 .always(function() {
5953 NETDATA.loadedRequiredJs++;
5956 NETDATA.loadRequiredJs(++index, callback);
5960 NETDATA.loadRequiredJs(++index, callback);
5963 NETDATA.loadRequiredCSS = function(index) {
5964 if(index >= NETDATA.requiredCSS.length)
5967 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5968 NETDATA.loadRequiredCSS(++index);
5972 if(NETDATA.options.debug.main_loop === true)
5973 console.log('loading ' + NETDATA.requiredCSS[index].url);
5975 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5976 NETDATA.loadRequiredCSS(++index);
5980 // ----------------------------------------------------------------------------------------------------------------
5981 // Registry of netdata hosts
5984 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5985 chart_div_offset: 100, // give that space above the chart when scrolling to it
5986 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5987 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5989 ms_penalty: 0, // the time penalty of the next alarm
5990 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5991 // if alarms are shown faster than: one per 500ms
5993 notifications: false, // when true, the browser supports notifications (may not be granted though)
5994 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5995 first_notification_id: 0, // the id of the first alarm_log entry for this session
5996 // this is used to prevent CLEAR notifications for past events
5997 // notifications_shown: new Array(),
5999 server: null, // the server to connect to for fetching alarms
6000 current: null, // the list of raised alarms - updated in the background
6001 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6003 notify: function(entry) {
6004 // console.log('alarm ' + entry.unique_id);
6006 if(entry.updated === true) {
6007 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6011 var value = entry.value;
6012 if(NETDATA.alarms.current !== null) {
6013 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6014 if(typeof t !== 'undefined' && entry.status == t.status)
6018 var name = entry.name.replace(/_/g, ' ');
6019 var status = entry.status.toLowerCase();
6020 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6021 var tag = entry.alarm_id;
6022 var icon = 'images/seo-performance-128.png';
6023 var interaction = false;
6027 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6029 switch(entry.status) {
6037 case 'UNINITIALIZED':
6041 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6042 // console.log('alarm ' + entry.unique_id + ' is not current');
6045 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6046 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6049 title = name + ' back to normal';
6050 icon = 'images/check-mark-2-128-green.png'
6051 interaction = false;
6055 if(entry.old_status === 'CRITICAL')
6056 status = 'demoted to ' + entry.status.toLowerCase();
6058 icon = 'images/alert-128-orange.png';
6059 interaction = false;
6063 if(entry.old_status === 'WARNING')
6064 status = 'escalated to ' + entry.status.toLowerCase();
6066 icon = 'images/alert-128-red.png'
6071 console.log('invalid alarm status ' + entry.status);
6076 // cleanup old notifications with the same alarm_id as this one
6077 // FIXME: it does not seem to work on any web browser!
6078 var len = NETDATA.alarms.notifications_shown.length;
6080 var n = NETDATA.alarms.notifications_shown[len];
6081 if(n.data.alarm_id === entry.alarm_id) {
6082 console.log('removing old alarm ' + n.data.unique_id);
6084 // close the notification
6087 // remove it from the array
6088 NETDATA.alarms.notifications_shown.splice(len, 1);
6089 len = NETDATA.alarms.notifications_shown.length;
6096 setTimeout(function() {
6097 // show this notification
6098 // console.log('new notification: ' + title);
6099 var n = new Notification(title, {
6100 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6102 requireInteraction: interaction,
6103 icon: NETDATA.serverDefault + icon,
6107 n.onclick = function(event) {
6108 event.preventDefault();
6109 NETDATA.alarms.onclick(event.target.data);
6113 // NETDATA.alarms.notifications_shown.push(n);
6114 // console.log(entry);
6115 }, NETDATA.alarms.ms_penalty);
6117 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6121 scrollToChart: function(chart_id) {
6122 if(typeof chart_id === 'string') {
6123 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6124 if(typeof offset !== 'undefined') {
6125 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6132 scrollToAlarm: function(alarm) {
6133 if(typeof alarm === 'object') {
6134 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6136 if(ret === true && NETDATA.options.page_is_visible === false)
6138 // 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.');
6143 notifyAll: function() {
6144 // console.log('FETCHING ALARM LOG');
6145 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6146 // console.log('ALARM LOG FETCHED');
6148 if(data === null || typeof data !== 'object') {
6149 console.log('invalid alarms log response');
6153 if(data.length === 0) {
6154 console.log('received empty alarm log');
6158 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6160 data.sort(function(a, b) {
6161 if(a.unique_id > b.unique_id) return -1;
6162 if(a.unique_id < b.unique_id) return 1;
6166 NETDATA.alarms.ms_penalty = 0;
6168 var len = data.length;
6170 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6171 NETDATA.alarms.notify(data[len]);
6174 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6177 NETDATA.alarms.last_notification_id = data[0].unique_id;
6178 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6179 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6183 check_notifications: function() {
6184 // returns true if we should fire 1+ notifications
6186 if(NETDATA.alarms.notifications !== true) {
6187 // console.log('notifications not available');
6191 if(Notification.permission !== 'granted') {
6192 // console.log('notifications not granted');
6196 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6197 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6199 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6200 // console.log('new alarms detected');
6203 //else console.log('no new alarms');
6205 // else console.log('cannot process alarms');
6210 get: function(what, callback) {
6212 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6216 'Cache-Control': 'no-cache, no-store',
6217 'Pragma': 'no-cache'
6219 xhrFields: { withCredentials: true } // required for the cookie
6221 .done(function(data) {
6222 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6223 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6225 if(typeof callback === 'function')
6229 NETDATA.error(415, NETDATA.alarms.server);
6231 if(typeof callback === 'function')
6236 update_forever: function() {
6237 NETDATA.alarms.get('active', function(data) {
6239 NETDATA.alarms.current = data;
6241 if(NETDATA.alarms.check_notifications() === true) {
6242 NETDATA.alarms.notifyAll();
6245 if (typeof NETDATA.alarms.callback === 'function') {
6246 NETDATA.alarms.callback(data);
6249 // Health monitoring is disabled on this netdata
6250 if(data.status === false) return;
6253 setTimeout(NETDATA.alarms.update_forever, 10000);
6257 get_log: function(last_id, callback) {
6258 // console.log('fetching all log after ' + last_id.toString());
6260 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6264 'Cache-Control': 'no-cache, no-store',
6265 'Pragma': 'no-cache'
6267 xhrFields: { withCredentials: true } // required for the cookie
6269 .done(function(data) {
6270 if(typeof callback === 'function')
6274 NETDATA.error(416, NETDATA.alarms.server);
6276 if(typeof callback === 'function')
6282 var host = NETDATA.serverDefault;
6283 while(host.slice(-1) === '/')
6284 host = host.substring(0, host.length - 1);
6285 NETDATA.alarms.server = host;
6287 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6289 if(NETDATA.alarms.onclick === null)
6290 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6292 if(netdataShowAlarms === true) {
6293 NETDATA.alarms.update_forever();
6295 if('Notification' in window) {
6296 // console.log('notifications available');
6297 NETDATA.alarms.notifications = true;
6299 if(Notification.permission === 'default')
6300 Notification.requestPermission();
6306 // ----------------------------------------------------------------------------------------------------------------
6307 // Registry of netdata hosts
6309 NETDATA.registry = {
6310 server: null, // the netdata registry server
6311 person_guid: null, // the unique ID of this browser / user
6312 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6313 hostname: null, // the hostname of the netdata server that served dashboard.js
6314 machines: null, // the user's other URLs
6315 machines_array: null, // the user's other URLs in an array
6318 parsePersonUrls: function(person_urls) {
6319 // console.log(person_urls);
6320 NETDATA.registry.person_urls = person_urls;
6323 NETDATA.registry.machines = {};
6324 NETDATA.registry.machines_array = new Array();
6326 var now = Date.now();
6327 var apu = person_urls;
6330 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6331 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6337 accesses: apu[i][3],
6339 alternate_urls: new Array()
6341 obj.alternate_urls.push(apu[i][1]);
6343 NETDATA.registry.machines[apu[i][0]] = obj;
6344 NETDATA.registry.machines_array.push(obj);
6347 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6349 var pu = NETDATA.registry.machines[apu[i][0]];
6350 if(pu.last_t < apu[i][2]) {
6352 pu.last_t = apu[i][2];
6353 pu.name = apu[i][4];
6355 pu.accesses += apu[i][3];
6356 pu.alternate_urls.push(apu[i][1]);
6361 if(typeof netdataRegistryCallback === 'function')
6362 netdataRegistryCallback(NETDATA.registry.machines_array);
6366 if(netdataRegistry !== true) return;
6368 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6370 NETDATA.registry.server = data.registry;
6371 NETDATA.registry.machine_guid = data.machine_guid;
6372 NETDATA.registry.hostname = data.hostname;
6374 NETDATA.registry.access(2, function (person_urls) {
6375 NETDATA.registry.parsePersonUrls(person_urls);
6382 hello: function(host, callback) {
6383 while(host.slice(-1) === '/')
6384 host = host.substring(0, host.length - 1);
6386 // send HELLO to a netdata server:
6387 // 1. verifies the server is reachable
6388 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6390 url: host + '/api/v1/registry?action=hello',
6394 'Cache-Control': 'no-cache, no-store',
6395 'Pragma': 'no-cache'
6397 xhrFields: { withCredentials: true } // required for the cookie
6399 .done(function(data) {
6400 if(typeof data.status !== 'string' || data.status !== 'ok') {
6401 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6405 if(typeof callback === 'function')
6409 NETDATA.error(407, host);
6411 if(typeof callback === 'function')
6416 access: function(max_redirects, callback) {
6417 // send ACCESS to a netdata registry:
6418 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6419 // 2. it responds with a list of netdata servers we know
6420 // the registry identifies us using a cookie it sets the first time we access it
6421 // the registry may respond with a redirect URL to send us to another registry
6423 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),
6427 'Cache-Control': 'no-cache, no-store',
6428 'Pragma': 'no-cache'
6430 xhrFields: { withCredentials: true } // required for the cookie
6432 .done(function(data) {
6433 var redirect = null;
6434 if(typeof data.registry === 'string')
6435 redirect = data.registry;
6437 if(typeof data.status !== 'string' || data.status !== 'ok') {
6438 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6443 if(redirect !== null && max_redirects > 0) {
6444 NETDATA.registry.server = redirect;
6445 NETDATA.registry.access(max_redirects - 1, callback);
6448 if(typeof callback === 'function')
6453 if(typeof data.person_guid === 'string')
6454 NETDATA.registry.person_guid = data.person_guid;
6456 if(typeof callback === 'function')
6457 callback(data.urls);
6461 NETDATA.error(410, NETDATA.registry.server);
6463 if(typeof callback === 'function')
6468 delete: function(delete_url, callback) {
6469 // send DELETE to a netdata registry:
6471 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),
6475 'Cache-Control': 'no-cache, no-store',
6476 'Pragma': 'no-cache'
6478 xhrFields: { withCredentials: true } // required for the cookie
6480 .done(function(data) {
6481 if(typeof data.status !== 'string' || data.status !== 'ok') {
6482 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6486 if(typeof callback === 'function')
6490 NETDATA.error(412, NETDATA.registry.server);
6492 if(typeof callback === 'function')
6497 search: function(machine_guid, callback) {
6498 // SEARCH for the URLs of a machine:
6500 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,
6504 'Cache-Control': 'no-cache, no-store',
6505 'Pragma': 'no-cache'
6507 xhrFields: { withCredentials: true } // required for the cookie
6509 .done(function(data) {
6510 if(typeof data.status !== 'string' || data.status !== 'ok') {
6511 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6515 if(typeof callback === 'function')
6519 NETDATA.error(418, NETDATA.registry.server);
6521 if(typeof callback === 'function')
6526 switch: function(new_person_guid, callback) {
6529 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,
6533 'Cache-Control': 'no-cache, no-store',
6534 'Pragma': 'no-cache'
6536 xhrFields: { withCredentials: true } // required for the cookie
6538 .done(function(data) {
6539 if(typeof data.status !== 'string' || data.status !== 'ok') {
6540 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6544 if(typeof callback === 'function')
6548 NETDATA.error(414, NETDATA.registry.server);
6550 if(typeof callback === 'function')
6556 // ----------------------------------------------------------------------------------------------------------------
6559 if(typeof netdataPrepCallback === 'function')
6560 netdataPrepCallback();
6562 NETDATA.errorReset();
6563 NETDATA.loadRequiredCSS(0);
6565 NETDATA._loadjQuery(function() {
6566 NETDATA.loadRequiredJs(0, function() {
6567 if(typeof $().emulateTransitionEnd !== 'function') {
6568 // bootstrap is not available
6569 NETDATA.options.current.show_help = false;
6572 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6573 if(NETDATA.options.debug.main_loop === true)
6574 console.log('starting chart refresh thread');
6581 // window.NETDATA = NETDATA;
6582 // })(window, document);