1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
4 /*global netdataNoDygraphs *//* boolean, disable dygraph charts
6 /*global netdataNoSparklines *//* boolean, disable sparkline charts
8 /*global netdataNoPeitys *//* boolean, disable peity charts
10 /*global netdataNoGoogleCharts *//* boolean, disable google charts
12 /*global netdataNoMorris *//* boolean, disable morris charts
14 /*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
16 /*global netdataNoGauge *//* boolean, disable gauge.js charts
18 /*global netdataNoD3 *//* boolean, disable d3 charts
20 /*global netdataNoC3 *//* boolean, disable c3 charts
22 /*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
24 /*global netdataDontStart *//* boolean, do not start the thread to process the charts
26 /*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
28 /*global netdataRegistry:true *//* boolean, use the netdata registry
30 /*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
31 * (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
34 /*global netdataShowHelp:true *//* boolean, disable charts help
36 /*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
40 /*global netdataCallback *//* function, callback to be called when netdata is ready to start
42 * netdata will be running while this is called
43 * (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
46 /*global netdataServer *//* string, the URL of the netdata server to use
47 * (default: the URL the page is hosted at) */
49 // ----------------------------------------------------------------------------
52 var NETDATA = window.NETDATA || {};
54 (function(window, document) {
55 // ------------------------------------------------------------------------
56 // compatibility fixes
58 // fix IE issue with console
59 if(!window.console) { window.console = { log: function(){} }; }
61 // if string.endsWith is not defined, define it
62 if(typeof String.prototype.endsWith !== 'function') {
63 String.prototype.endsWith = function(s) {
64 if(s.length > this.length) return false;
65 return this.slice(-s.length) === s;
69 // if string.startsWith is not defined, define it
70 if(typeof String.prototype.startsWith !== 'function') {
71 String.prototype.startsWith = function(s) {
72 if(s.length > this.length) return false;
73 return this.slice(s.length) === s;
77 NETDATA.name2id = function(s) {
86 // ----------------------------------------------------------------------------------------------------------------
87 // Detect the netdata server
89 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91 NETDATA._scriptSource = function() {
94 if(typeof document.currentScript !== 'undefined') {
95 script = document.currentScript;
98 var all_scripts = document.getElementsByTagName('script');
99 script = all_scripts[all_scripts.length - 1];
102 if (typeof script.getAttribute.length !== 'undefined')
105 script = script.getAttribute('src', -1);
110 if(typeof netdataServer !== 'undefined')
111 NETDATA.serverDefault = netdataServer;
113 var s = NETDATA._scriptSource();
114 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
116 console.log('WARNING: Cannot detect the URL of the netdata server.');
117 NETDATA.serverDefault = null;
121 if(NETDATA.serverDefault === null)
122 NETDATA.serverDefault = '';
123 else if(NETDATA.serverDefault.slice(-1) !== '/')
124 NETDATA.serverDefault += '/';
126 // default URLs for all the external files we need
127 // make them RELATIVE so that the whole thing can also be
128 // installed under a web server
129 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-1.3.1.min.js';
134 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
135 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
136 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
137 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
138 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
139 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
140 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
141 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
142 NETDATA.google_js = 'https://www.google.com/jsapi';
146 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
147 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
148 background: '#FFFFFF',
149 foreground: '#000000',
152 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
153 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
154 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
155 '#329262', '#3B3EAC' ],
156 easypiechart_track: '#f0f0f0',
157 easypiechart_scale: '#dfe0e0',
158 gauge_pointer: '#C0C0C0',
159 gauge_stroke: '#F0F0F0',
160 gauge_gradient: false
163 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
164 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
165 background: '#272b30',
166 foreground: '#C8C8C8',
169 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
170 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
171 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
172 '#a6a479', '#a66da8' ],
174 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
175 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
176 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
177 '#329262', '#3B3EFF' ],
178 easypiechart_track: '#373b40',
179 easypiechart_scale: '#373b40',
180 gauge_pointer: '#474b50',
181 gauge_stroke: '#373b40',
182 gauge_gradient: false
186 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
187 NETDATA.themes.current = NETDATA.themes[netdataTheme];
189 NETDATA.themes.current = NETDATA.themes.white;
191 NETDATA.colors = NETDATA.themes.current.colors;
193 // these are the colors Google Charts are using
194 // we have them here to attempt emulate their look and feel on the other chart libraries
195 // http://there4.io/2012/05/02/google-chart-color-list/
196 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
197 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
198 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
200 // an alternative set
201 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
202 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
203 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
205 if(typeof netdataShowHelp === 'undefined')
206 netdataShowHelp = true;
208 if(typeof netdataShowAlarms === 'undefined')
209 netdataShowAlarms = false;
211 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
212 netdataRegistryAfterMs = 1500;
214 if(typeof netdataRegistry === 'undefined') {
215 // backward compatibility
216 netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
218 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
219 netdataRegistry = true;
222 // ----------------------------------------------------------------------------------------------------------------
223 // detect if this is probably a slow device
225 var isSlowDeviceResult = undefined;
226 var isSlowDevice = function() {
227 if(isSlowDeviceResult !== undefined)
228 return isSlowDeviceResult;
231 var ua = navigator.userAgent.toLowerCase();
233 var iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
234 var android = /android/.test(ua) && !window.MSStream;
235 isSlowDeviceResult = (iOS === true || android === true);
238 isSlowDeviceResult = false;
241 console.log('isSlowDevice(): ' + isSlowDeviceResult);
242 return isSlowDeviceResult;
245 // ----------------------------------------------------------------------------------------------------------------
246 // the defaults for all charts
248 // if the user does not specify any of these, the following will be used
250 NETDATA.chartDefaults = {
251 host: NETDATA.serverDefault, // the server to get data from
252 width: '100%', // the chart width - can be null
253 height: '100%', // the chart height - can be null
254 min_width: null, // the chart minimum width - can be null
255 library: 'dygraph', // the graphing library to use
256 method: 'average', // the grouping method
257 before: 0, // panning
258 after: -600, // panning
259 pixels_per_point: 1, // the detail of the chart
260 fill_luminance: 0.8 // luminance of colors in solid areas
263 // ----------------------------------------------------------------------------------------------------------------
267 pauseCallback: null, // a callback when we are really paused
269 pause: false, // when enabled we don't auto-refresh the charts
271 targets: null, // an array of all the state objects that are
272 // currently active (independently of their
273 // viewport visibility)
275 updated_dom: true, // when true, the DOM has been updated with
276 // new elements we have to check.
278 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
279 // rendering charts continuously.
280 // used with .current.fast_render_timeframe
282 page_is_visible: true, // when true, this page is visible
284 auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
285 // auto-refresher for some time (when a chart is
286 // performing pan or zoom, we need to stop refreshing
287 // all other charts, to have the maximum speed for
288 // rendering the chart that is panned or zoomed).
289 // Used with .current.global_pan_sync_time
291 last_resized: Date.now(), // the timestamp of the last resize request
293 last_page_scroll: 0, // the timestamp the last time the page was scrolled
295 // the current profile
296 // we may have many...
298 pixels_per_point: isSlowDevice()?5:1, // the minimum pixels per point for all charts
299 // increase this to speed javascript up
300 // each chart library has its own limit too
301 // the max of this and the chart library is used
302 // the final is calculated every time, so a change
303 // here will have immediate effect on the next chart
306 idle_between_charts: 100, // ms - how much time to wait between chart updates
308 fast_render_timeframe: 200, // ms - render continuously until this time of continuous
309 // rendering has been reached
310 // this setting is used to make it render e.g. 10
311 // charts at once, sleep idle_between_charts time
312 // and continue for another 10 charts.
314 idle_between_loops: 500, // ms - if all charts have been updated, wait this
315 // time before starting again.
317 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
319 idle_lost_focus: 500, // ms - when the window does not have focus, check
320 // if focus has been regained, every this time
322 global_pan_sync_time: 1000, // ms - when you pan or zoom a chart, the background
323 // auto-refreshing of charts is paused for this amount
326 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
327 // of time before setting up synchronized selections
330 sync_selection: true, // enable or disable selection sync
332 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
334 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
336 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
338 update_only_visible: true, // enable or disable visibility management
340 parallel_refresher: (isSlowDevice() === false), // enable parallel refresh of charts
342 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
344 destroy_on_hide: (isSlowDevice() === true), // destroy charts when they are not visible
346 show_help: netdataShowHelp, // when enabled the charts will show some help
347 show_help_delay_show_ms: 500,
348 show_help_delay_hide_ms: 0,
350 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
352 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
353 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
355 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
357 smooth_plot: (isSlowDevice() === false), // enable smooth plot, where possible
359 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
361 color_fill_opacity_line: 1.0,
362 color_fill_opacity_area: 0.2,
363 color_fill_opacity_stacked: 0.8,
365 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
366 pan_and_zoom_factor_multiplier_control: 2.0,
367 pan_and_zoom_factor_multiplier_shift: 3.0,
368 pan_and_zoom_factor_multiplier_alt: 4.0,
370 abort_ajax_on_scroll: false, // kill pending ajax page scroll
371 async_on_scroll: false, // sync/async onscroll handler
372 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
374 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
376 setOptionCallback: function() { }
384 chart_data_url: false,
385 chart_errors: false, // FIXME: remember to set it to false before merging
393 NETDATA.statistics = {
396 refreshes_active_max: 0
400 // ----------------------------------------------------------------------------------------------------------------
401 // local storage options
403 NETDATA.localStorage = {
406 callback: {} // only used for resetting back to defaults
409 NETDATA.localStorageTested = -1;
410 NETDATA.localStorageTest = function() {
411 if(NETDATA.localStorageTested !== -1)
412 return NETDATA.localStorageTested;
414 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
417 localStorage.setItem(test, test);
418 localStorage.removeItem(test);
419 NETDATA.localStorageTested = true;
422 NETDATA.localStorageTested = false;
426 NETDATA.localStorageTested = false;
428 return NETDATA.localStorageTested;
431 NETDATA.localStorageGet = function(key, def, callback) {
434 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
435 NETDATA.localStorage.default[key.toString()] = def;
436 NETDATA.localStorage.callback[key.toString()] = callback;
439 if(NETDATA.localStorageTest() === true) {
441 // console.log('localStorage: loading "' + key.toString() + '"');
442 ret = localStorage.getItem(key.toString());
443 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
444 if(ret === null || ret === 'undefined') {
445 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
446 localStorage.setItem(key.toString(), JSON.stringify(def));
450 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
451 ret = JSON.parse(ret);
452 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
456 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
461 if(typeof ret === 'undefined' || ret === 'undefined') {
462 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
466 NETDATA.localStorage.current[key.toString()] = ret;
470 NETDATA.localStorageSet = function(key, value, callback) {
471 if(typeof value === 'undefined' || value === 'undefined') {
472 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
475 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
476 NETDATA.localStorage.default[key.toString()] = value;
477 NETDATA.localStorage.current[key.toString()] = value;
478 NETDATA.localStorage.callback[key.toString()] = callback;
481 if(NETDATA.localStorageTest() === true) {
482 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
484 localStorage.setItem(key.toString(), JSON.stringify(value));
487 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
491 NETDATA.localStorage.current[key.toString()] = value;
495 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
496 var keys = Object.keys(obj);
497 var len = keys.length;
501 if(typeof obj[i] === 'object') {
502 //console.log('object ' + prefix + '.' + i.toString());
503 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
507 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
511 NETDATA.setOption = function(key, value) {
512 if(key.toString() === 'setOptionCallback') {
513 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
514 NETDATA.options.current[key.toString()] = value;
515 NETDATA.options.current.setOptionCallback();
518 else if(NETDATA.options.current[key.toString()] !== value) {
519 var name = 'options.' + key.toString();
521 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
522 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
524 //console.log(NETDATA.localStorage);
525 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
526 //console.log(NETDATA.options);
527 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
529 if(typeof NETDATA.options.current.setOptionCallback === 'function')
530 NETDATA.options.current.setOptionCallback();
536 NETDATA.getOption = function(key) {
537 return NETDATA.options.current[key.toString()];
540 // read settings from local storage
541 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
543 // always start with this option enabled.
544 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
546 NETDATA.resetOptions = function() {
547 var keys = Object.keys(NETDATA.localStorage.default);
548 var len = keys.length;
551 var a = i.split('.');
553 if(a[0] === 'options') {
554 if(a[1] === 'setOptionCallback') continue;
555 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
556 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
558 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
560 else if(a[0] === 'chart_heights') {
561 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
562 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
568 // ----------------------------------------------------------------------------------------------------------------
570 if(NETDATA.options.debug.main_loop === true)
571 console.log('welcome to NETDATA');
573 NETDATA.onresizeCallback = null;
574 NETDATA.onresize = function() {
575 NETDATA.options.last_resized = Date.now();
578 if(typeof NETDATA.onresizeCallback === 'function')
579 NETDATA.onresizeCallback();
582 NETDATA.onscroll_updater_count = 0;
583 NETDATA.onscroll_updater_running = false;
584 NETDATA.onscroll_updater_last_run = 0;
585 NETDATA.onscroll_updater_watchdog = null;
586 NETDATA.onscroll_updater_max_duration = 0;
587 NETDATA.onscroll_updater_above_threshold_count = 0;
588 NETDATA.onscroll_updater = function() {
589 NETDATA.onscroll_updater_running = true;
590 NETDATA.onscroll_updater_count++;
591 var start = Date.now();
593 var targets = NETDATA.options.targets;
594 var len = targets.length;
596 // when the user scrolls he sees that we have
597 // hidden all the not-visible charts
598 // using this little function we try to switch
599 // the charts back to visible quickly
602 if(NETDATA.options.abort_ajax_on_scroll === true) {
603 // we have to cancel pending requests too
606 if (targets[len]._updating === true) {
607 if (typeof targets[len].xhr !== 'undefined') {
608 targets[len].xhr.abort();
609 targets[len].running = false;
610 targets[len]._updating = false;
612 targets[len].isVisible();
617 // just find which chart is visible
620 targets[len].isVisible();
623 var end = Date.now();
624 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
626 if(NETDATA.options.current.async_on_scroll === false) {
627 var dt = end - start;
628 if(dt > NETDATA.onscroll_updater_max_duration) {
629 // console.log('max onscroll event handler duration increased to ' + dt);
630 NETDATA.onscroll_updater_max_duration = dt;
633 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
634 // console.log('slow: ' + dt);
635 NETDATA.onscroll_updater_above_threshold_count++;
637 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
638 NETDATA.setOption('async_on_scroll', true);
639 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
644 NETDATA.onscroll_updater_last_run = start;
645 NETDATA.onscroll_updater_running = false;
648 NETDATA.onscroll = function() {
649 // console.log('onscroll');
651 NETDATA.options.last_page_scroll = Date.now();
652 NETDATA.options.auto_refresher_stop_until = 0;
654 if(NETDATA.options.targets === null) return;
656 if(NETDATA.options.current.async_on_scroll === true) {
658 if(NETDATA.onscroll_updater_running === false) {
659 NETDATA.onscroll_updater_running = true;
660 setTimeout(NETDATA.onscroll_updater, 0);
663 if(NETDATA.onscroll_updater_watchdog !== null)
664 clearTimeout(NETDATA.onscroll_updater_watchdog);
666 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
667 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
668 // console.log('watchdog');
669 NETDATA.onscroll_updater();
672 NETDATA.onscroll_updater_watchdog = null;
678 NETDATA.onscroll_updater();
682 window.onresize = NETDATA.onresize;
683 window.onscroll = NETDATA.onscroll;
685 // ----------------------------------------------------------------------------------------------------------------
688 NETDATA.errorCodes = {
689 100: { message: "Cannot load chart library", alert: true },
690 101: { message: "Cannot load jQuery", alert: true },
691 402: { message: "Chart library not found", alert: false },
692 403: { message: "Chart library not enabled/is failed", alert: false },
693 404: { message: "Chart not found", alert: false },
694 405: { message: "Cannot download charts index from server", alert: true },
695 406: { message: "Invalid charts index downloaded from server", alert: true },
696 407: { message: "Cannot HELLO netdata server", alert: false },
697 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
698 409: { message: "Cannot ACCESS netdata registry", alert: false },
699 410: { message: "Netdata registry ACCESS failed", alert: false },
700 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
701 412: { message: "Netdata registry DELETE failed", alert: false },
702 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
703 414: { message: "Netdata registry SWITCH failed", alert: false },
704 415: { message: "Netdata alarms download failed", alert: false },
705 416: { message: "Netdata alarms log download failed", alert: false },
706 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
707 418: { message: "Netdata registry SEARCH failed", alert: false }
709 NETDATA.errorLast = {
715 NETDATA.error = function(code, msg) {
716 NETDATA.errorLast.code = code;
717 NETDATA.errorLast.message = msg;
718 NETDATA.errorLast.datetime = Date.now();
720 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
723 if(typeof netdataErrorCallback === 'function') {
724 ret = netdataErrorCallback('system', code, msg);
727 if(ret && NETDATA.errorCodes[code].alert)
728 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
731 NETDATA.errorReset = function() {
732 NETDATA.errorLast.code = 0;
733 NETDATA.errorLast.message = "You are doing fine!";
734 NETDATA.errorLast.datetime = 0;
737 // ----------------------------------------------------------------------------------------------------------------
738 // commonMin & commonMax
740 NETDATA.commonMin = {
744 get: function(state) {
745 if(typeof state.__commonMin === 'undefined') {
746 // get the commonMin setting
747 var self = $(state.element);
748 state.__commonMin = self.data('common-min') || null;
751 var min = state.data.min;
752 var name = state.__commonMin;
755 // we don't need commonMin
756 //state.log('no need for commonMin');
760 var t = this.keys[name];
761 if(typeof t === 'undefined') {
763 this.keys[name] = {};
767 var uuid = state.uuid;
768 if(typeof t[uuid] !== 'undefined') {
769 if(t[uuid] === min) {
770 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
771 return this.latest[name];
773 else if(min < this.latest[name]) {
774 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
776 this.latest[name] = min;
784 // find the common min
787 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
789 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
790 this.latest[name] = m;
795 NETDATA.commonMax = {
799 get: function(state) {
800 if(typeof state.__commonMax === 'undefined') {
801 // get the commonMax setting
802 var self = $(state.element);
803 state.__commonMax = self.data('common-max') || null;
806 var max = state.data.max;
807 var name = state.__commonMax;
810 // we don't need commonMax
811 //state.log('no need for commonMax');
815 var t = this.keys[name];
816 if(typeof t === 'undefined') {
818 this.keys[name] = {};
822 var uuid = state.uuid;
823 if(typeof t[uuid] !== 'undefined') {
824 if(t[uuid] === max) {
825 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
826 return this.latest[name];
828 else if(max > this.latest[name]) {
829 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
831 this.latest[name] = max;
839 // find the common max
842 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
844 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
845 this.latest[name] = m;
850 // ----------------------------------------------------------------------------------------------------------------
853 // When multiple charts need the same chart, we avoid downloading it
854 // multiple times (and having it in browser memory multiple time)
855 // by using this registry.
857 // Every time we download a chart definition, we save it here with .add()
858 // Then we try to get it back with .get(). If that fails, we download it.
860 NETDATA.fixHost = function(host) {
861 while(host.slice(-1) === '/')
862 host = host.substring(0, host.length - 1);
867 NETDATA.chartRegistry = {
870 fixid: function(id) {
871 return id.replace(/:/g, "_").replace(/\//g, "_");
874 add: function(host, id, data) {
875 host = this.fixid(host);
878 if(typeof this.charts[host] === 'undefined')
879 this.charts[host] = {};
881 //console.log('added ' + host + '/' + id);
882 this.charts[host][id] = data;
885 get: function(host, id) {
886 host = this.fixid(host);
889 if(typeof this.charts[host] === 'undefined')
892 if(typeof this.charts[host][id] === 'undefined')
895 //console.log('cached ' + host + '/' + id);
896 return this.charts[host][id];
899 downloadAll: function(host, callback) {
900 host = NETDATA.fixHost(host);
905 url: host + '/api/v1/charts',
908 xhrFields: { withCredentials: true } // required for the cookie
910 .done(function(data) {
912 var h = NETDATA.chartRegistry.fixid(host);
913 self.charts[h] = data.charts;
915 else NETDATA.error(406, host + '/api/v1/charts');
917 if(typeof callback === 'function')
918 return callback(data);
921 NETDATA.error(405, host + '/api/v1/charts');
923 if(typeof callback === 'function')
924 return callback(null);
929 // ----------------------------------------------------------------------------------------------------------------
930 // Global Pan and Zoom on charts
932 // Using this structure are synchronize all the charts, so that
933 // when you pan or zoom one, all others are automatically refreshed
934 // to the same timespan.
936 NETDATA.globalPanAndZoom = {
937 seq: 0, // timestamp ms
938 // every time a chart is panned or zoomed
939 // we set the timestamp here
940 // then we use it as a sequence number
941 // to find if other charts are synchronized
942 // to this time-range
944 master: null, // the master chart (state), to which all others
947 force_before_ms: null, // the timespan to sync all other charts
948 force_after_ms: null,
953 setMaster: function(state, after, before) {
954 if(NETDATA.options.current.sync_pan_and_zoom === false)
957 if(this.master !== null && this.master !== state)
958 this.master.resetChart(true, true);
960 var now = Date.now();
963 this.force_after_ms = after;
964 this.force_before_ms = before;
965 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
967 if(typeof this.callback === 'function')
968 this.callback(true, after, before);
972 clearMaster: function() {
973 if(this.master !== null) {
974 var st = this.master;
981 this.force_after_ms = null;
982 this.force_before_ms = null;
983 NETDATA.options.auto_refresher_stop_until = 0;
985 if(typeof this.callback === 'function')
986 this.callback(false, 0, 0);
989 // is the given state the master of the global
990 // pan and zoom sync?
991 isMaster: function(state) {
992 return (this.master === state);
995 // are we currently have a global pan and zoom sync?
996 isActive: function() {
997 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
1000 // check if a chart, other than the master
1001 // needs to be refreshed, due to the global pan and zoom
1002 shouldBeAutoRefreshed: function(state) {
1003 if(this.master === null || this.seq === 0)
1006 //if(state.needsRecreation())
1009 return (state.tm.pan_and_zoom_seq !== this.seq);
1013 // ----------------------------------------------------------------------------------------------------------------
1014 // dimensions selection
1017 // move color assignment to dimensions, here
1019 var dimensionStatus = function(parent, label, name_div, value_div, color) {
1020 this.enabled = false;
1021 this.parent = parent;
1023 this.name_div = null;
1024 this.value_div = null;
1025 this.color = NETDATA.themes.current.foreground;
1026 this.selected = (parent.unselected_count === 0);
1028 this.setOptions(name_div, value_div, color);
1031 dimensionStatus.prototype.invalidate = function() {
1032 this.name_div = null;
1033 this.value_div = null;
1034 this.enabled = false;
1037 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1040 if(this.name_div !== name_div) {
1041 this.name_div = name_div;
1042 this.name_div.title = this.label;
1043 this.name_div.style.color = this.color;
1044 if(this.selected === false)
1045 this.name_div.className = 'netdata-legend-name not-selected';
1047 this.name_div.className = 'netdata-legend-name selected';
1050 if(this.value_div !== value_div) {
1051 this.value_div = value_div;
1052 this.value_div.title = this.label;
1053 this.value_div.style.color = this.color;
1054 if(this.selected === false)
1055 this.value_div.className = 'netdata-legend-value not-selected';
1057 this.value_div.className = 'netdata-legend-value selected';
1060 this.enabled = true;
1064 dimensionStatus.prototype.setHandler = function() {
1065 if(this.enabled === false) return;
1069 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1070 this.name_div.onclick = this.value_div.onclick = function(e) {
1072 if(ds.isSelected()) {
1074 if(e.shiftKey === true || e.ctrlKey === true) {
1075 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1078 if(ds.parent.countSelected() === 0)
1079 ds.parent.selectAll();
1082 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1083 if(ds.parent.countSelected() === 1) {
1084 ds.parent.selectAll();
1087 ds.parent.selectNone();
1093 // this is not selected
1094 if(e.shiftKey === true || e.ctrlKey === true) {
1095 // control or shift key is pressed -> select this too
1099 // no key is pressed -> select only this
1100 ds.parent.selectNone();
1105 ds.parent.state.redrawChart();
1109 dimensionStatus.prototype.select = function() {
1110 if(this.enabled === false) return;
1112 this.name_div.className = 'netdata-legend-name selected';
1113 this.value_div.className = 'netdata-legend-value selected';
1114 this.selected = true;
1117 dimensionStatus.prototype.unselect = function() {
1118 if(this.enabled === false) return;
1120 this.name_div.className = 'netdata-legend-name not-selected';
1121 this.value_div.className = 'netdata-legend-value hidden';
1122 this.selected = false;
1125 dimensionStatus.prototype.isSelected = function() {
1126 return(this.enabled === true && this.selected === true);
1129 // ----------------------------------------------------------------------------------------------------------------
1131 var dimensionsVisibility = function(state) {
1134 this.dimensions = {};
1135 this.selected_count = 0;
1136 this.unselected_count = 0;
1139 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1140 if(typeof this.dimensions[label] === 'undefined') {
1142 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1145 this.dimensions[label].setOptions(name_div, value_div, color);
1147 return this.dimensions[label];
1150 dimensionsVisibility.prototype.dimensionGet = function(label) {
1151 return this.dimensions[label];
1154 dimensionsVisibility.prototype.invalidateAll = function() {
1155 var keys = Object.keys(this.dimensions);
1156 var len = keys.length;
1158 this.dimensions[keys[len]].invalidate();
1161 dimensionsVisibility.prototype.selectAll = function() {
1162 var keys = Object.keys(this.dimensions);
1163 var len = keys.length;
1165 this.dimensions[keys[len]].select();
1168 dimensionsVisibility.prototype.countSelected = function() {
1170 var keys = Object.keys(this.dimensions);
1171 var len = keys.length;
1173 if(this.dimensions[keys[len]].isSelected()) selected++;
1178 dimensionsVisibility.prototype.selectNone = function() {
1179 var keys = Object.keys(this.dimensions);
1180 var len = keys.length;
1182 this.dimensions[keys[len]].unselect();
1185 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1187 this.selected_count = 0;
1188 this.unselected_count = 0;
1190 var len = array.length;
1192 var ds = this.dimensions[array[len]];
1193 if(typeof ds === 'undefined') {
1194 // console.log(array[i] + ' is not found');
1197 else if(ds.isSelected()) {
1199 this.selected_count++;
1203 this.unselected_count++;
1207 if(this.selected_count === 0 && this.unselected_count !== 0) {
1209 return this.selected2BooleanArray(array);
1216 // ----------------------------------------------------------------------------------------------------------------
1217 // global selection sync
1219 NETDATA.globalSelectionSync = {
1221 dont_sync_before: 0,
1226 if(this.state !== null)
1227 this.state.globalSelectionSyncStop();
1231 if(this.state !== null) {
1232 this.state.globalSelectionSyncDelay();
1237 // ----------------------------------------------------------------------------------------------------------------
1238 // Our state object, where all per-chart values are stored
1240 var chartState = function(element) {
1241 var self = $(element);
1242 this.element = element;
1245 // all private functions should use 'that', instead of 'this'
1248 /* error() - private
1249 * show an error instead of the chart
1251 var error = function(msg) {
1254 if(typeof netdataErrorCallback === 'function') {
1255 ret = netdataErrorCallback('chart', that.id, msg);
1259 that.element.innerHTML = that.id + ': ' + msg;
1260 that.enabled = false;
1261 that.current = that.pan;
1265 // GUID - a unique identifier for the chart
1266 this.uuid = NETDATA.guid();
1268 // string - the name of chart
1269 this.id = self.data('netdata');
1271 // string - the key for localStorage settings
1272 this.settings_id = self.data('id') || null;
1274 // the user given dimensions of the element
1275 this.width = self.data('width') || NETDATA.chartDefaults.width;
1276 this.height = self.data('height') || NETDATA.chartDefaults.height;
1277 this.height_original = this.height;
1279 if(this.settings_id !== null) {
1280 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1281 // this is the callback that will be called
1282 // if and when the user resets all localStorage variables
1283 // to their defaults
1285 resizeChartToHeight(height);
1289 // string - the netdata server URL, without any path
1290 this.host = self.data('host') || NETDATA.chartDefaults.host;
1292 // make sure the host does not end with /
1293 // all netdata API requests use absolute paths
1294 while(this.host.slice(-1) === '/')
1295 this.host = this.host.substring(0, this.host.length - 1);
1297 // string - the grouping method requested by the user
1298 this.method = self.data('method') || NETDATA.chartDefaults.method;
1300 // the time-range requested by the user
1301 this.after = self.data('after') || NETDATA.chartDefaults.after;
1302 this.before = self.data('before') || NETDATA.chartDefaults.before;
1304 // the pixels per point requested by the user
1305 this.pixels_per_point = self.data('pixels-per-point') || 1;
1306 this.points = self.data('points') || null;
1308 // the dimensions requested by the user
1309 this.dimensions = self.data('dimensions') || null;
1311 // the chart library requested by the user
1312 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1314 // how many retries we have made to load chart data from the server
1315 this.retries_on_data_failures = 0;
1317 // object - the chart library used
1318 this.library = null;
1322 this.colors_assigned = {};
1323 this.colors_available = null;
1325 // the element already created by the user
1326 this.element_message = null;
1328 // the element with the chart
1329 this.element_chart = null;
1331 // the element with the legend of the chart (if created by us)
1332 this.element_legend = null;
1333 this.element_legend_childs = {
1338 perfect_scroller: null, // the container to apply perfect scroller to
1342 this.chart_url = null; // string - the url to download chart info
1343 this.chart = null; // object - the chart as downloaded from the server
1345 this.title = self.data('title') || null; // the title of the chart
1346 this.units = self.data('units') || null; // the units of the chart dimensions
1347 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1348 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1350 this.running = false; // boolean - true when the chart is being refreshed now
1351 this.enabled = true; // boolean - is the chart enabled for refresh?
1352 this.paused = false; // boolean - is the chart paused for any reason?
1353 this.selected = false; // boolean - is the chart shown a selection?
1354 this.debug = false; // boolean - console.log() debug info about this chart
1356 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1357 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1358 this.requested_after = null; // milliseconds - the timestamp of the request after param
1359 this.requested_before = null; // milliseconds - the timestamp of the request before param
1360 this.requested_padding = null;
1361 this.view_after = 0;
1362 this.view_before = 0;
1364 this.value_decimal_detail = -1;
1365 var d = self.data('decimal-digits');
1366 if(typeof d === 'number') {
1367 this.value_decimal_detail = 1;
1369 this.value_decimal_detail *= 10;
1375 force_update_at: 0, // the timestamp to force the update at
1376 force_before_ms: null,
1377 force_after_ms: null
1382 force_update_at: 0, // the timestamp to force the update at
1383 force_before_ms: null,
1384 force_after_ms: null
1389 force_update_at: 0, // the timestamp to force the update at
1390 force_before_ms: null,
1391 force_after_ms: null
1394 // this is a pointer to one of the sub-classes below
1396 this.current = this.auto;
1398 // check the requested library is available
1399 // we don't initialize it here - it will be initialized when
1400 // this chart will be first used
1401 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1402 NETDATA.error(402, that.library_name);
1403 error('chart library "' + that.library_name + '" is not found');
1406 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1407 NETDATA.error(403, that.library_name);
1408 error('chart library "' + that.library_name + '" is not enabled');
1412 that.library = NETDATA.chartLibraries[that.library_name];
1414 // milliseconds - the time the last refresh took
1415 this.refresh_dt_ms = 0;
1417 // if we need to report the rendering speed
1418 // find the element that needs to be updated
1419 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1421 if(refresh_dt_element_name !== null) {
1422 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1425 this.refresh_dt_element = null;
1427 this.dimensions_visibility = new dimensionsVisibility(this);
1429 this._updating = false;
1431 // ============================================================================================================
1432 // PRIVATE FUNCTIONS
1434 var createDOM = function() {
1435 if(that.enabled === false) return;
1437 if(that.element_message !== null) that.element_message.innerHTML = '';
1438 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1439 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1441 that.element.innerHTML = '';
1443 that.element_message = document.createElement('div');
1444 that.element_message.className = 'netdata-message icon hidden';
1445 that.element.appendChild(that.element_message);
1447 that.element_chart = document.createElement('div');
1448 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1449 that.element.appendChild(that.element_chart);
1451 if(that.hasLegend() === true) {
1452 that.element.className = "netdata-container-with-legend";
1453 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1455 that.element_legend = document.createElement('div');
1456 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1457 that.element.appendChild(that.element_legend);
1460 that.element.className = "netdata-container";
1461 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1463 that.element_legend = null;
1465 that.element_legend_childs.series = null;
1467 if(typeof(that.width) === 'string')
1468 $(that.element).css('width', that.width);
1469 else if(typeof(that.width) === 'number')
1470 $(that.element).css('width', that.width + 'px');
1472 if(typeof(that.library.aspect_ratio) === 'undefined') {
1473 if(typeof(that.height) === 'string')
1474 that.element.style.height = that.height;
1475 else if(typeof(that.height) === 'number')
1476 that.element.style.height = that.height.toString() + 'px';
1479 var w = that.element.offsetWidth;
1480 if(w === null || w === 0) {
1481 // the div is hidden
1482 // this will resize the chart when next viewed
1483 that.tm.last_resized = 0;
1486 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1489 if(NETDATA.chartDefaults.min_width !== null)
1490 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1492 that.tm.last_dom_created = Date.now();
1498 * initialize state variables
1499 * destroy all (possibly) created state elements
1500 * create the basic DOM for a chart
1502 var init = function() {
1503 if(that.enabled === false) return;
1505 that.paused = false;
1506 that.selected = false;
1508 that.chart_created = false; // boolean - is the library.create() been called?
1509 that.updates_counter = 0; // numeric - the number of refreshes made so far
1510 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1511 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1514 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1515 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1516 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1518 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1519 last_updated: 0, // the timestamp the chart last updated with data
1520 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1522 // Used with NETDATA.globalPanAndZoom.seq
1523 last_visible_check: 0, // the time we last checked if it is visible
1524 last_resized: 0, // the time the chart was resized
1525 last_hidden: 0, // the time the chart was hidden
1526 last_unhidden: 0, // the time the chart was unhidden
1527 last_autorefreshed: 0 // the time the chart was last refreshed
1530 that.data = null; // the last data as downloaded from the netdata server
1531 that.data_url = 'invalid://'; // string - the last url used to update the chart
1532 that.data_points = 0; // number - the number of points returned from netdata
1533 that.data_after = 0; // milliseconds - the first timestamp of the data
1534 that.data_before = 0; // milliseconds - the last timestamp of the data
1535 that.data_update_every = 0; // milliseconds - the frequency to update the data
1537 that.tm.last_initialized = Date.now();
1540 that.setMode('auto');
1543 var maxMessageFontSize = function() {
1544 var screenHeight = screen.height;
1545 var el = that.element;
1547 // normally we want a font size, as tall as the element
1548 var h = el.clientHeight;
1550 // but give it some air, 20% let's say, or 5 pixels min
1551 var lost = Math.max(h * 0.2, 5);
1554 // center the text, vertically
1555 var paddingTop = (lost - 5) / 2;
1557 // but check the width too
1558 // it should fit 10 characters in it
1559 var w = el.clientWidth / 10;
1561 paddingTop += (h - w) / 2;
1565 // and don't make it too huge
1566 // 5% of the screen size is good
1567 if(h > screenHeight / 20) {
1568 paddingTop += (h - (screenHeight / 20)) / 2;
1569 h = screenHeight / 20;
1573 that.element_message.style.fontSize = h.toString() + 'px';
1574 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1577 var showMessageIcon = function(icon) {
1578 that.element_message.innerHTML = icon;
1579 maxMessageFontSize();
1580 $(that.element_message).removeClass('hidden');
1581 that.___messageHidden___ = undefined;
1584 var hideMessage = function() {
1585 if(typeof that.___messageHidden___ === 'undefined') {
1586 that.___messageHidden___ = true;
1587 $(that.element_message).addClass('hidden');
1591 var showRendering = function() {
1593 if(that.chart !== null) {
1594 if(that.chart.chart_type === 'line')
1595 icon = '<i class="fa fa-line-chart"></i>';
1597 icon = '<i class="fa fa-area-chart"></i>';
1600 icon = '<i class="fa fa-area-chart"></i>';
1602 showMessageIcon(icon + ' netdata');
1605 var showLoading = function() {
1606 if(that.chart_created === false) {
1607 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1613 var isHidden = function() {
1614 return (typeof that.___chartIsHidden___ !== 'undefined');
1617 // hide the chart, when it is not visible - called from isVisible()
1618 var hideChart = function() {
1619 // hide it, if it is not already hidden
1620 if(isHidden() === true) return;
1622 if(that.chart_created === true) {
1623 if(NETDATA.options.current.destroy_on_hide === true) {
1624 // we should destroy it
1629 that.element_chart.style.display = 'none';
1630 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1631 that.tm.last_hidden = Date.now();
1634 // This works, but I not sure there are no corner cases somewhere
1635 // so it is commented - if the user has memory issues he can
1636 // set Destroy on Hide for all charts
1637 // that.data = null;
1641 that.___chartIsHidden___ = true;
1644 // unhide the chart, when it is visible - called from isVisible()
1645 var unhideChart = function() {
1646 if(isHidden() === false) return;
1648 that.___chartIsHidden___ = undefined;
1649 that.updates_since_last_unhide = 0;
1651 if(that.chart_created === false) {
1652 // we need to re-initialize it, to show our background
1653 // logo in bootstrap tabs, until the chart loads
1657 that.tm.last_unhidden = Date.now();
1658 that.element_chart.style.display = '';
1659 if(that.element_legend !== null) that.element_legend.style.display = '';
1665 var canBeRendered = function() {
1666 return (isHidden() === false && that.isVisible(true) === true);
1669 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1670 var callChartLibraryUpdateSafely = function(data) {
1673 if(canBeRendered() === false)
1676 if(NETDATA.options.debug.chart_errors === true)
1677 status = that.library.update(that, data);
1680 status = that.library.update(that, data);
1687 if(status === false) {
1688 error('chart failed to be updated as ' + that.library_name);
1695 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1696 var callChartLibraryCreateSafely = function(data) {
1699 if(canBeRendered() === false)
1702 if(NETDATA.options.debug.chart_errors === true)
1703 status = that.library.create(that, data);
1706 status = that.library.create(that, data);
1713 if(status === false) {
1714 error('chart failed to be created as ' + that.library_name);
1718 that.chart_created = true;
1719 that.updates_since_last_creation = 0;
1723 // ----------------------------------------------------------------------------------------------------------------
1726 // resizeChart() - private
1727 // to be called just before the chart library to make sure that
1728 // a properly sized dom is available
1729 var resizeChart = function() {
1730 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1731 if(that.chart_created === false) return;
1733 if(that.needsRecreation()) {
1736 else if(typeof that.library.resize === 'function') {
1737 that.library.resize(that);
1739 if(that.element_legend_childs.perfect_scroller !== null)
1740 Ps.update(that.element_legend_childs.perfect_scroller);
1742 maxMessageFontSize();
1745 that.tm.last_resized = Date.now();
1749 // this is the actual chart resize algorithm
1751 // - resize the entire container
1752 // - update the internal states
1753 // - resize the chart as the div changes height
1754 // - update the scrollbar of the legend
1755 var resizeChartToHeight = function(h) {
1757 that.element.style.height = h;
1759 if(that.settings_id !== null)
1760 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1762 var now = Date.now();
1763 NETDATA.options.last_page_scroll = now;
1764 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1767 that.tm.last_resized = 0;
1771 this.resizeHandler = function(e) {
1774 if(typeof this.event_resize === 'undefined'
1775 || this.event_resize.chart_original_w === 'undefined'
1776 || this.event_resize.chart_original_h === 'undefined')
1777 this.event_resize = {
1778 chart_original_w: this.element.clientWidth,
1779 chart_original_h: this.element.clientHeight,
1783 if(e.type === 'touchstart') {
1784 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1785 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1788 this.event_resize.mouse_start_x = e.clientX;
1789 this.event_resize.mouse_start_y = e.clientY;
1792 this.event_resize.chart_start_w = this.element.clientWidth;
1793 this.event_resize.chart_start_h = this.element.clientHeight;
1794 this.event_resize.chart_last_w = this.element.clientWidth;
1795 this.event_resize.chart_last_h = this.element.clientHeight;
1797 var now = Date.now();
1798 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1799 // double click / double tap event
1801 // console.dir(this.element_legend_childs.content);
1802 // console.dir(this.element_legend_childs.perfect_scroller);
1804 // the optimal height of the chart
1805 // showing the entire legend
1806 var optimal = this.event_resize.chart_last_h
1807 + this.element_legend_childs.perfect_scroller.scrollHeight
1808 - this.element_legend_childs.perfect_scroller.clientHeight;
1810 // if we are not optimal, be optimal
1811 if(this.event_resize.chart_last_h !== optimal) {
1812 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1813 resizeChartToHeight(optimal.toString() + 'px');
1816 // else if the current height is not the original/saved height
1817 // reset to the original/saved height
1818 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1819 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1820 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1823 // else if the current height is not the internal default height
1824 // reset to the internal default height
1825 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1826 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1827 resizeChartToHeight(this.height_original.toString());
1830 // else if the current height is not the firstchild's clientheight
1832 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1833 var parent_rect = this.element.getBoundingClientRect();
1834 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1835 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1837 // console.log(parent_rect);
1838 // console.log(content_rect);
1839 // console.log(wanted);
1841 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1842 if(this.event_resize.chart_last_h !== wanted)
1843 resizeChartToHeight(wanted.toString() + 'px');
1847 this.event_resize.last = now;
1849 // process movement event
1850 document.onmousemove =
1851 document.ontouchmove =
1852 this.element_legend_childs.resize_handler.onmousemove =
1853 this.element_legend_childs.resize_handler.ontouchmove =
1858 case 'mousemove': y = e.clientY; break;
1859 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1863 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1865 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1866 resizeChartToHeight(newH.toString() + 'px');
1867 that.event_resize.chart_last_h = newH;
1872 // process end event
1873 document.onmouseup =
1874 document.ontouchend =
1875 this.element_legend_childs.resize_handler.onmouseup =
1876 this.element_legend_childs.resize_handler.ontouchend =
1880 // remove all the hooks
1881 document.onmouseup =
1882 document.onmousemove =
1883 document.ontouchmove =
1884 document.ontouchend =
1885 that.element_legend_childs.resize_handler.onmousemove =
1886 that.element_legend_childs.resize_handler.ontouchmove =
1887 that.element_legend_childs.resize_handler.onmouseout =
1888 that.element_legend_childs.resize_handler.onmouseup =
1889 that.element_legend_childs.resize_handler.ontouchend =
1892 // allow auto-refreshes
1893 NETDATA.options.auto_refresher_stop_until = 0;
1899 var noDataToShow = function() {
1900 showMessageIcon('<i class="fa fa-warning"></i> empty');
1901 that.legendUpdateDOM();
1902 that.tm.last_autorefreshed = Date.now();
1903 // that.data_update_every = 30 * 1000;
1904 //that.element_chart.style.display = 'none';
1905 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1906 //that.___chartIsHidden___ = true;
1909 // ============================================================================================================
1912 this.error = function(msg) {
1916 this.setMode = function(m) {
1917 if(this.current !== null && this.current.name === m) return;
1920 this.current = this.auto;
1921 else if(m === 'pan')
1922 this.current = this.pan;
1923 else if(m === 'zoom')
1924 this.current = this.zoom;
1926 this.current = this.auto;
1928 this.current.force_update_at = 0;
1929 this.current.force_before_ms = null;
1930 this.current.force_after_ms = null;
1932 this.tm.last_mode_switch = Date.now();
1935 // ----------------------------------------------------------------------------------------------------------------
1936 // global selection sync
1938 // prevent to global selection sync for some time
1939 this.globalSelectionSyncDelay = function(ms) {
1940 if(NETDATA.options.current.sync_selection === false)
1943 if(typeof ms === 'number')
1944 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1946 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1949 // can we globally apply selection sync?
1950 this.globalSelectionSyncAbility = function() {
1951 if(NETDATA.options.current.sync_selection === false)
1954 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1957 this.globalSelectionSyncIsMaster = function() {
1958 return (NETDATA.globalSelectionSync.state === this);
1961 // this chart is the master of the global selection sync
1962 this.globalSelectionSyncBeMaster = function() {
1964 if(this.globalSelectionSyncIsMaster()) {
1965 if(this.debug === true)
1966 this.log('sync: I am the master already.');
1971 if(NETDATA.globalSelectionSync.state) {
1972 if(this.debug === true)
1973 this.log('sync: I am not the sync master. Resetting global sync.');
1975 this.globalSelectionSyncStop();
1978 // become the master
1979 if(this.debug === true)
1980 this.log('sync: becoming sync master.');
1982 this.selected = true;
1983 NETDATA.globalSelectionSync.state = this;
1985 // find the all slaves
1986 var targets = NETDATA.options.targets;
1987 var len = targets.length;
1989 var st = targets[len];
1992 if(this.debug === true)
1993 st.log('sync: not adding me to sync');
1995 else if(st.globalSelectionSyncIsEligible()) {
1996 if(this.debug === true)
1997 st.log('sync: adding to sync as slave');
1999 st.globalSelectionSyncBeSlave();
2003 // this.globalSelectionSyncDelay(100);
2006 // can the chart participate to the global selection sync as a slave?
2007 this.globalSelectionSyncIsEligible = function() {
2008 return (this.enabled === true
2009 && this.library !== null
2010 && typeof this.library.setSelection === 'function'
2011 && this.isVisible() === true
2012 && this.chart_created === true);
2015 // this chart becomes a slave of the global selection sync
2016 this.globalSelectionSyncBeSlave = function() {
2017 if(NETDATA.globalSelectionSync.state !== this)
2018 NETDATA.globalSelectionSync.slaves.push(this);
2021 // sync all the visible charts to the given time
2022 // this is to be called from the chart libraries
2023 this.globalSelectionSync = function(t) {
2024 if(this.globalSelectionSyncAbility() === false)
2027 if(this.globalSelectionSyncIsMaster() === false) {
2028 if(this.debug === true)
2029 this.log('sync: trying to be sync master.');
2031 this.globalSelectionSyncBeMaster();
2033 if(this.globalSelectionSyncAbility() === false)
2037 NETDATA.globalSelectionSync.last_t = t;
2038 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2043 // stop syncing all charts to the given time
2044 this.globalSelectionSyncStop = function() {
2045 if(NETDATA.globalSelectionSync.slaves.length) {
2046 if(this.debug === true)
2047 this.log('sync: cleaning up...');
2049 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2051 if(that.debug === true)
2052 st.log('sync: not adding me to sync stop');
2055 if(that.debug === true)
2056 st.log('sync: removed slave from sync');
2058 st.clearSelection();
2062 NETDATA.globalSelectionSync.last_t = 0;
2063 NETDATA.globalSelectionSync.slaves = [];
2064 NETDATA.globalSelectionSync.state = null;
2067 this.clearSelection();
2070 this.setSelection = function(t) {
2071 if(typeof this.library.setSelection === 'function')
2072 this.selected = (this.library.setSelection(this, t) === true);
2074 this.selected = true;
2076 if(this.selected === true && this.debug === true)
2077 this.log('selection set to ' + t.toString());
2079 return this.selected;
2082 this.clearSelection = function() {
2083 if(this.selected === true) {
2084 if(typeof this.library.clearSelection === 'function')
2085 this.selected = (this.library.clearSelection(this) !== true);
2087 this.selected = false;
2089 if(this.selected === false && this.debug === true)
2090 this.log('selection cleared');
2095 return this.selected;
2098 // find if a timestamp (ms) is shown in the current chart
2099 this.timeIsVisible = function(t) {
2100 return (t >= this.data_after && t <= this.data_before);
2103 this.calculateRowForTime = function(t) {
2104 if(this.timeIsVisible(t) === false) return -1;
2105 return Math.floor((t - this.data_after) / this.data_update_every);
2108 // ----------------------------------------------------------------------------------------------------------------
2111 this.log = function(msg) {
2112 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2115 this.pauseChart = function() {
2116 if(this.paused === false) {
2117 if(this.debug === true)
2118 this.log('pauseChart()');
2124 this.unpauseChart = function() {
2125 if(this.paused === true) {
2126 if(this.debug === true)
2127 this.log('unpauseChart()');
2129 this.paused = false;
2133 this.resetChart = function(dont_clear_master, dont_update) {
2134 if(this.debug === true)
2135 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2137 if(typeof dont_clear_master === 'undefined')
2138 dont_clear_master = false;
2140 if(typeof dont_update === 'undefined')
2141 dont_update = false;
2143 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2144 if(this.debug === true)
2145 this.log('resetChart() diverting to clearMaster().');
2146 // this will call us back with master === true
2147 NETDATA.globalPanAndZoom.clearMaster();
2151 this.clearSelection();
2153 this.tm.pan_and_zoom_seq = 0;
2155 this.setMode('auto');
2156 this.current.force_update_at = 0;
2157 this.current.force_before_ms = null;
2158 this.current.force_after_ms = null;
2159 this.tm.last_autorefreshed = 0;
2160 this.paused = false;
2161 this.selected = false;
2162 this.enabled = true;
2163 // this.debug = false;
2165 // do not update the chart here
2166 // or the chart will flip-flop when it is the master
2167 // of a selection sync and another chart becomes
2170 if(dont_update !== true && this.isVisible() === true) {
2175 this.updateChartPanOrZoom = function(after, before) {
2176 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2179 if(this.debug === true)
2182 if(before < after) {
2183 if(this.debug === true)
2184 this.log(logme + 'flipped parameters, rejecting it.');
2189 if(typeof this.fixed_min_duration === 'undefined')
2190 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2192 var min_duration = this.fixed_min_duration;
2193 var current_duration = Math.round(this.view_before - this.view_after);
2195 // round the numbers
2196 after = Math.round(after);
2197 before = Math.round(before);
2199 // align them to update_every
2200 // stretching them further away
2201 after -= after % this.data_update_every;
2202 before += this.data_update_every - (before % this.data_update_every);
2204 // the final wanted duration
2205 var wanted_duration = before - after;
2207 // to allow panning, accept just a point below our minimum
2208 if((current_duration - this.data_update_every) < min_duration)
2209 min_duration = current_duration - this.data_update_every;
2211 // we do it, but we adjust to minimum size and return false
2212 // when the wanted size is below the current and the minimum
2214 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2215 if(this.debug === true)
2216 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2218 min_duration = this.fixed_min_duration;
2220 var dt = (min_duration - wanted_duration) / 2;
2223 wanted_duration = before - after;
2227 var tolerance = this.data_update_every * 2;
2228 var movement = Math.abs(before - this.view_before);
2230 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2231 if(this.debug === true)
2232 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);
2236 if(this.current.name === 'auto') {
2237 this.log(logme + 'caller called me with mode: ' + this.current.name);
2238 this.setMode('pan');
2241 if(this.debug === true)
2242 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);
2244 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2245 this.current.force_after_ms = after;
2246 this.current.force_before_ms = before;
2247 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2251 this.legendFormatValue = function(value) {
2252 if(value === null || value === 'undefined') return '-';
2253 if(typeof value !== 'number') return value;
2255 if(this.value_decimal_detail !== -1)
2256 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2258 var abs = Math.abs(value);
2259 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2260 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2261 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2262 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2263 return (Math.round(value * 10000) / 10000).toLocaleString();
2266 this.legendSetLabelValue = function(label, value) {
2267 var series = this.element_legend_childs.series[label];
2268 if(typeof series === 'undefined') return;
2269 if(series.value === null && series.user === null) return;
2272 // this slows down firefox and edge significantly
2273 // since it requires to use innerHTML(), instead of innerText()
2275 // if the value has not changed, skip DOM update
2276 //if(series.last === value) return;
2279 if(typeof value === 'number') {
2280 var v = Math.abs(value);
2281 s = r = this.legendFormatValue(value);
2283 if(typeof series.last === 'number') {
2284 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2285 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2286 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2288 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2298 series.last = value;
2302 var s = this.legendFormatValue(value);
2304 // caching: do not update the update to show the same value again
2305 if(s === series.last_shown_value) return;
2306 series.last_shown_value = s;
2308 if(series.value !== null) series.value.innerText = s;
2309 if(series.user !== null) series.user.innerText = s;
2312 this.__legendSetDateString = function(date) {
2313 if(date !== this.__last_shown_legend_date) {
2314 this.element_legend_childs.title_date.innerText = date;
2315 this.__last_shown_legend_date = date;
2319 this.__legendSetTimeString = function(time) {
2320 if(time !== this.__last_shown_legend_time) {
2321 this.element_legend_childs.title_time.innerText = time;
2322 this.__last_shown_legend_time = time;
2326 this.__legendSetUnitsString = function(units) {
2327 if(units !== this.__last_shown_legend_units) {
2328 this.element_legend_childs.title_units.innerText = units;
2329 this.__last_shown_legend_units = units;
2333 this.legendSetDate = function(ms) {
2334 if(typeof ms !== 'number') {
2335 this.legendShowUndefined();
2339 var d = new Date(ms);
2341 if(this.element_legend_childs.title_date)
2342 this.__legendSetDateString(d.toLocaleDateString());
2344 if(this.element_legend_childs.title_time)
2345 this.__legendSetTimeString(d.toLocaleTimeString());
2347 if(this.element_legend_childs.title_units)
2348 this.__legendSetUnitsString(this.units)
2351 this.legendShowUndefined = function() {
2352 if(this.element_legend_childs.title_date)
2353 this.__legendSetDateString(' ');
2355 if(this.element_legend_childs.title_time)
2356 this.__legendSetTimeString(this.chart.name);
2358 if(this.element_legend_childs.title_units)
2359 this.__legendSetUnitsString(' ');
2361 if(this.data && this.element_legend_childs.series !== null) {
2362 var labels = this.data.dimension_names;
2363 var i = labels.length;
2365 var label = labels[i];
2367 if(typeof label === 'undefined') continue;
2368 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2369 this.legendSetLabelValue(label, null);
2374 this.legendShowLatestValues = function() {
2375 if(this.chart === null) return;
2376 if(this.selected) return;
2378 if(this.data === null || this.element_legend_childs.series === null) {
2379 this.legendShowUndefined();
2383 var show_undefined = true;
2384 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2385 show_undefined = false;
2387 if(show_undefined) {
2388 this.legendShowUndefined();
2392 this.legendSetDate(this.view_before);
2394 var labels = this.data.dimension_names;
2395 var i = labels.length;
2397 var label = labels[i];
2399 if(typeof label === 'undefined') continue;
2400 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2403 this.legendSetLabelValue(label, null);
2405 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2409 this.legendReset = function() {
2410 this.legendShowLatestValues();
2413 // this should be called just ONCE per dimension per chart
2414 this._chartDimensionColor = function(label) {
2415 if(this.colors === null) this.chartColors();
2417 if(typeof this.colors_assigned[label] === 'undefined') {
2418 if(this.colors_available.length === 0) {
2419 var len = NETDATA.themes.current.colors.length;
2421 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2424 this.colors_assigned[label] = this.colors_available.shift();
2426 if(this.debug === true)
2427 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2430 if(this.debug === true)
2431 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2434 this.colors.push(this.colors_assigned[label]);
2435 return this.colors_assigned[label];
2438 this.chartColors = function() {
2439 if(this.colors !== null) return this.colors;
2442 this.colors_available = [];
2444 // add the standard colors
2445 var len = NETDATA.themes.current.colors.length;
2447 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2449 // add the user supplied colors
2450 var c = $(this.element).data('colors');
2451 // this.log('read colors: ' + c);
2452 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2453 if(typeof c !== 'string') {
2454 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2464 this.colors_available.unshift(c[len]);
2465 // this.log('adding color: ' + c[len]);
2474 this.legendUpdateDOM = function() {
2475 var needed = false, dim, keys, len, i;
2477 // check that the legend DOM is up to date for the downloaded dimensions
2478 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2479 // this.log('the legend does not have any series - requesting legend update');
2482 else if(this.data === null) {
2483 // this.log('the chart does not have any data - requesting legend update');
2486 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2490 var labels = this.data.dimension_names.toString();
2491 if(labels !== this.element_legend_childs.series.labels_key) {
2494 if(this.debug === true)
2495 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2499 if(needed === false) {
2500 // make sure colors available
2503 // do we have to update the current values?
2504 // we do this, only when the visible chart is current
2505 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2506 if(this.debug === true)
2507 this.log('chart is in latest position... updating values on legend...');
2509 //var labels = this.data.dimension_names;
2510 //var i = labels.length;
2512 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2516 if(this.colors === null) {
2517 // this is the first time we update the chart
2518 // let's assign colors to all dimensions
2519 if(this.library.track_colors() === true) {
2520 keys = Object.keys(this.chart.dimensions);
2522 for(i = 0; i < len ;i++)
2523 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2526 // we will re-generate the colors for the chart
2527 // based on the selected dimensions
2530 if(this.debug === true)
2531 this.log('updating Legend DOM');
2533 // mark all dimensions as invalid
2534 this.dimensions_visibility.invalidateAll();
2536 var genLabel = function(state, parent, dim, name, count) {
2537 var color = state._chartDimensionColor(name);
2539 var user_element = null;
2540 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2541 if(user_id === null)
2542 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2543 if(user_id !== null) {
2544 user_element = document.getElementById(user_id) || null;
2545 if (user_element === null)
2546 state.log('Cannot find element with id: ' + user_id);
2549 state.element_legend_childs.series[name] = {
2550 name: document.createElement('span'),
2551 value: document.createElement('span'),
2554 last_shown_value: null
2557 var label = state.element_legend_childs.series[name];
2559 // create the dimension visibility tracking for this label
2560 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2562 var rgb = NETDATA.colorHex2Rgb(color);
2563 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2564 + state.chart.chart_type
2565 + '" style="background-color: '
2566 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2567 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2569 var text = document.createTextNode(' ' + name);
2570 label.name.appendChild(text);
2573 parent.appendChild(document.createElement('br'));
2575 parent.appendChild(label.name);
2576 parent.appendChild(label.value);
2579 var content = document.createElement('div');
2581 if(this.hasLegend()) {
2582 this.element_legend_childs = {
2584 resize_handler: document.createElement('div'),
2585 toolbox: document.createElement('div'),
2586 toolbox_left: document.createElement('div'),
2587 toolbox_right: document.createElement('div'),
2588 toolbox_reset: document.createElement('div'),
2589 toolbox_zoomin: document.createElement('div'),
2590 toolbox_zoomout: document.createElement('div'),
2591 toolbox_volume: document.createElement('div'),
2592 title_date: document.createElement('span'),
2593 title_time: document.createElement('span'),
2594 title_units: document.createElement('span'),
2595 perfect_scroller: document.createElement('div'),
2599 this.element_legend.innerHTML = '';
2601 if(this.library.toolboxPanAndZoom !== null) {
2603 var get_pan_and_zoom_step = function(event) {
2605 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2607 else if (event.shiftKey)
2608 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2610 else if (event.altKey)
2611 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2614 return NETDATA.options.current.pan_and_zoom_factor;
2617 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2618 this.element.appendChild(this.element_legend_childs.toolbox);
2620 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2621 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2622 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2623 this.element_legend_childs.toolbox_left.onclick = function(e) {
2626 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2627 var before = that.view_before - step;
2628 var after = that.view_after - step;
2629 if(after >= that.netdata_first)
2630 that.library.toolboxPanAndZoom(that, after, before);
2632 if(NETDATA.options.current.show_help === true)
2633 $(this.element_legend_childs.toolbox_left).popover({
2638 placement: 'bottom',
2639 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2641 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>'
2645 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2646 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2647 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2648 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2650 NETDATA.resetAllCharts(that);
2652 if(NETDATA.options.current.show_help === true)
2653 $(this.element_legend_childs.toolbox_reset).popover({
2658 placement: 'bottom',
2659 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2660 title: 'Chart Reset',
2661 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>'
2664 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2665 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2666 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2667 this.element_legend_childs.toolbox_right.onclick = function(e) {
2669 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2670 var before = that.view_before + step;
2671 var after = that.view_after + step;
2672 if(before <= that.netdata_last)
2673 that.library.toolboxPanAndZoom(that, after, before);
2675 if(NETDATA.options.current.show_help === true)
2676 $(this.element_legend_childs.toolbox_right).popover({
2681 placement: 'bottom',
2682 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2684 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>'
2688 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2689 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2690 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2691 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2693 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2694 var before = that.view_before - dt;
2695 var after = that.view_after + dt;
2696 that.library.toolboxPanAndZoom(that, after, before);
2698 if(NETDATA.options.current.show_help === true)
2699 $(this.element_legend_childs.toolbox_zoomin).popover({
2704 placement: 'bottom',
2705 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2706 title: 'Chart Zoom In',
2707 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>'
2710 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2711 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2712 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2713 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2715 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);
2716 var before = that.view_before + dt;
2717 var after = that.view_after - dt;
2719 that.library.toolboxPanAndZoom(that, after, before);
2721 if(NETDATA.options.current.show_help === true)
2722 $(this.element_legend_childs.toolbox_zoomout).popover({
2727 placement: 'bottom',
2728 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2729 title: 'Chart Zoom Out',
2730 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>'
2733 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2734 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2735 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2736 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2737 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2738 //e.preventDefault();
2739 //alert('clicked toolbox_volume on ' + that.id);
2743 this.element_legend_childs.toolbox = null;
2744 this.element_legend_childs.toolbox_left = null;
2745 this.element_legend_childs.toolbox_reset = null;
2746 this.element_legend_childs.toolbox_right = null;
2747 this.element_legend_childs.toolbox_zoomin = null;
2748 this.element_legend_childs.toolbox_zoomout = null;
2749 this.element_legend_childs.toolbox_volume = null;
2752 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2753 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2754 this.element.appendChild(this.element_legend_childs.resize_handler);
2755 if(NETDATA.options.current.show_help === true)
2756 $(this.element_legend_childs.resize_handler).popover({
2761 placement: 'bottom',
2762 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2763 title: 'Chart Resize',
2764 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>'
2768 this.element_legend_childs.resize_handler.onmousedown =
2770 that.resizeHandler(e);
2774 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2775 that.resizeHandler(e);
2778 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2779 this.element_legend.appendChild(this.element_legend_childs.title_date);
2781 this.element_legend.appendChild(document.createElement('br'));
2783 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2784 this.element_legend.appendChild(this.element_legend_childs.title_time);
2786 this.element_legend.appendChild(document.createElement('br'));
2788 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2789 this.element_legend.appendChild(this.element_legend_childs.title_units);
2791 this.element_legend.appendChild(document.createElement('br'));
2793 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2794 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2796 content.className = 'netdata-legend-series-content';
2797 this.element_legend_childs.perfect_scroller.appendChild(content);
2799 if(NETDATA.options.current.show_help === true)
2800 $(content).popover({
2805 placement: 'bottom',
2806 title: 'Chart Legend',
2807 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2808 content: 'You can click or tap on the values or the labels to select dimensions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2812 this.element_legend_childs = {
2814 resize_handler: null,
2817 toolbox_right: null,
2818 toolbox_reset: null,
2819 toolbox_zoomin: null,
2820 toolbox_zoomout: null,
2821 toolbox_volume: null,
2825 perfect_scroller: null,
2831 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2832 if(this.debug === true)
2833 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2835 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2836 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2841 keys = Object.keys(this.chart.dimensions);
2842 for(i = 0, len = keys.length; i < len ;i++) {
2844 tmp.push(this.chart.dimensions[dim].name);
2845 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2847 this.element_legend_childs.series.labels_key = tmp.toString();
2848 if(this.debug === true)
2849 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2852 // create a hidden div to be used for hidding
2853 // the original legend of the chart library
2854 var el = document.createElement('div');
2855 if(this.element_legend !== null)
2856 this.element_legend.appendChild(el);
2857 el.style.display = 'none';
2859 this.element_legend_childs.hidden = document.createElement('div');
2860 el.appendChild(this.element_legend_childs.hidden);
2862 if(this.element_legend_childs.perfect_scroller !== null) {
2863 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2865 wheelPropagation: true,
2866 swipePropagation: true,
2867 minScrollbarLength: null,
2868 maxScrollbarLength: null,
2869 useBothWheelAxes: false,
2870 suppressScrollX: true,
2871 suppressScrollY: false,
2872 scrollXMarginOffset: 0,
2873 scrollYMarginOffset: 0,
2876 Ps.update(this.element_legend_childs.perfect_scroller);
2879 this.legendShowLatestValues();
2882 this.hasLegend = function() {
2883 if(typeof this.___hasLegendCache___ !== 'undefined')
2884 return this.___hasLegendCache___;
2887 if(this.library && this.library.legend(this) === 'right-side') {
2888 var legend = $(this.element).data('legend') || 'yes';
2889 if(legend === 'yes') leg = true;
2892 this.___hasLegendCache___ = leg;
2896 this.legendWidth = function() {
2897 return (this.hasLegend())?140:0;
2900 this.legendHeight = function() {
2901 return $(this.element).height();
2904 this.chartWidth = function() {
2905 return $(this.element).width() - this.legendWidth();
2908 this.chartHeight = function() {
2909 return $(this.element).height();
2912 this.chartPixelsPerPoint = function() {
2913 // force an options provided detail
2914 var px = this.pixels_per_point;
2916 if(this.library && px < this.library.pixels_per_point(this))
2917 px = this.library.pixels_per_point(this);
2919 if(px < NETDATA.options.current.pixels_per_point)
2920 px = NETDATA.options.current.pixels_per_point;
2925 this.needsRecreation = function() {
2927 this.chart_created === true
2929 && this.library.autoresize() === false
2930 && this.tm.last_resized < NETDATA.options.last_resized
2934 this.chartURL = function() {
2935 var after, before, points_multiplier = 1;
2936 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2937 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2939 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2940 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2941 this.view_after = after * 1000;
2942 this.view_before = before * 1000;
2944 this.requested_padding = null;
2945 points_multiplier = 1;
2947 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2948 this.tm.pan_and_zoom_seq = 0;
2950 before = Math.round(this.current.force_before_ms / 1000);
2951 after = Math.round(this.current.force_after_ms / 1000);
2952 this.view_after = after * 1000;
2953 this.view_before = before * 1000;
2955 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2956 this.requested_padding = Math.round((before - after) / 2);
2957 after -= this.requested_padding;
2958 before += this.requested_padding;
2959 this.requested_padding *= 1000;
2960 points_multiplier = 2;
2963 this.current.force_before_ms = null;
2964 this.current.force_after_ms = null;
2967 this.tm.pan_and_zoom_seq = 0;
2969 before = this.before;
2971 this.view_after = after * 1000;
2972 this.view_before = before * 1000;
2974 this.requested_padding = null;
2975 points_multiplier = 1;
2978 this.requested_after = after * 1000;
2979 this.requested_before = before * 1000;
2981 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2983 // build the data URL
2984 this.data_url = this.host + this.chart.data_url;
2985 this.data_url += "&format=" + this.library.format();
2986 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2987 this.data_url += "&group=" + this.method;
2989 if(this.override_options !== null)
2990 this.data_url += "&options=" + this.override_options.toString();
2992 this.data_url += "&options=" + this.library.options(this);
2994 this.data_url += '|jsonwrap';
2996 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2997 this.data_url += '|nonzero';
2999 if(this.append_options !== null)
3000 this.data_url += '|' + this.append_options.toString();
3003 this.data_url += "&after=" + after.toString();
3006 this.data_url += "&before=" + before.toString();
3009 this.data_url += "&dimensions=" + this.dimensions;
3011 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3012 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3015 this.redrawChart = function() {
3016 if(this.data !== null)
3017 this.updateChartWithData(this.data);
3020 this.updateChartWithData = function(data) {
3021 if(this.debug === true)
3022 this.log('updateChartWithData() called.');
3024 // this may force the chart to be re-created
3028 this.updates_counter++;
3029 this.updates_since_last_unhide++;
3030 this.updates_since_last_creation++;
3032 var started = Date.now();
3034 // if the result is JSON, find the latest update-every
3035 this.data_update_every = data.view_update_every * 1000;
3036 this.data_after = data.after * 1000;
3037 this.data_before = data.before * 1000;
3038 this.netdata_first = data.first_entry * 1000;
3039 this.netdata_last = data.last_entry * 1000;
3040 this.data_points = data.points;
3043 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3044 if(this.view_after < this.data_after) {
3045 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3046 this.view_after = this.data_after;
3049 if(this.view_before > this.data_before) {
3050 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3051 this.view_before = this.data_before;
3055 this.view_after = this.data_after;
3056 this.view_before = this.data_before;
3059 if(this.debug === true) {
3060 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3062 if(this.current.force_after_ms)
3063 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3065 this.log('STATUS: forced : unset');
3067 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3068 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3069 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3070 this.log('STATUS: points : ' + (this.data_points).toString());
3073 if(this.data_points === 0) {
3078 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3079 if(this.debug === true)
3080 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3082 this.chart_created = false;
3085 // check and update the legend
3086 this.legendUpdateDOM();
3088 if(this.chart_created === true
3089 && typeof this.library.update === 'function') {
3091 if(this.debug === true)
3092 this.log('updating chart...');
3094 if(callChartLibraryUpdateSafely(data) === false)
3098 if(this.debug === true)
3099 this.log('creating chart...');
3101 if(callChartLibraryCreateSafely(data) === false)
3105 this.legendShowLatestValues();
3106 if(this.selected === true)
3107 NETDATA.globalSelectionSync.stop();
3109 // update the performance counters
3110 var now = Date.now();
3111 this.tm.last_updated = now;
3113 // don't update last_autorefreshed if this chart is
3114 // forced to be updated with global PanAndZoom
3115 if(NETDATA.globalPanAndZoom.isActive())
3116 this.tm.last_autorefreshed = 0;
3118 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3119 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3121 this.tm.last_autorefreshed = now;
3124 this.refresh_dt_ms = now - started;
3125 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3127 if(this.refresh_dt_element !== null)
3128 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3131 this.updateChart = function(callback) {
3132 if(this.debug === true)
3133 this.log('updateChart() called.');
3135 if(this._updating === true) {
3136 if(this.debug === true)
3137 this.log('I am already updating...');
3139 if(typeof callback === 'function')
3145 // due to late initialization of charts and libraries
3146 // we need to check this too
3147 if(this.enabled === false) {
3148 if(this.debug === true)
3149 this.log('I am not enabled');
3151 if(typeof callback === 'function')
3157 if(canBeRendered() === false) {
3158 if(typeof callback === 'function')
3164 if(this.chart === null)
3165 return this.getChart(function() {
3166 return that.updateChart(callback);
3169 if(this.library.initialized === false) {
3170 if(this.library.enabled === true) {
3171 return this.library.initialize(function () {
3172 return that.updateChart(callback);
3176 error('chart library "' + this.library_name + '" is not available.');
3178 if(typeof callback === 'function')
3185 this.clearSelection();
3188 if(this.debug === true)
3189 this.log('updating from ' + this.data_url);
3191 NETDATA.statistics.refreshes_total++;
3192 NETDATA.statistics.refreshes_active++;
3194 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3195 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3197 this._updating = true;
3199 this.xhr = $.ajax( {
3204 'Cache-Control': 'no-cache, no-store',
3205 'Pragma': 'no-cache'
3207 xhrFields: { withCredentials: true } // required for the cookie
3209 .done(function(data) {
3210 that.xhr = undefined;
3211 that.retries_on_data_failures = 0;
3213 if(that.debug === true)
3214 that.log('data received. updating chart.');
3216 that.updateChartWithData(data);
3218 .fail(function(msg) {
3219 that.xhr = undefined;
3221 if(msg.statusText !== 'abort') {
3222 that.retries_on_data_failures++;
3223 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3224 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3225 that.retries_on_data_failures = 0;
3226 error('data download failed for url: ' + that.data_url);
3229 that.tm.last_autorefreshed = Date.now();
3230 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3234 .always(function() {
3235 that.xhr = undefined;
3237 NETDATA.statistics.refreshes_active--;
3238 that._updating = false;
3240 if(typeof callback === 'function')
3245 this.isVisible = function(nocache) {
3246 if(typeof nocache === 'undefined')
3249 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3251 // caching - we do not evaluate the charts visibility
3252 // if the page has not been scrolled since the last check
3253 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3254 return this.___isVisible___;
3256 this.tm.last_visible_check = Date.now();
3258 var wh = window.innerHeight;
3259 var x = this.element.getBoundingClientRect();
3263 if(x.width === 0 || x.height === 0) {
3265 this.___isVisible___ = false;
3266 return this.___isVisible___;
3269 if(x.top < 0 && -x.top > x.height) {
3270 // the chart is entirely above
3271 ret = -x.top - x.height;
3273 else if(x.top > wh) {
3274 // the chart is entirely below
3278 if(ret > tolerance) {
3279 // the chart is too far
3282 this.___isVisible___ = false;
3283 return this.___isVisible___;
3286 // the chart is inside or very close
3289 this.___isVisible___ = true;
3290 return this.___isVisible___;
3294 this.isAutoRefreshable = function() {
3295 return (this.current.autorefresh);
3298 this.canBeAutoRefreshed = function() {
3299 var now = Date.now();
3301 if(this.running === true) {
3302 if(this.debug === true)
3303 this.log('I am already running');
3308 if(this.enabled === false) {
3309 if(this.debug === true)
3310 this.log('I am not enabled');
3315 if(this.library === null || this.library.enabled === false) {
3316 error('charting library "' + this.library_name + '" is not available');
3317 if(this.debug === true)
3318 this.log('My chart library ' + this.library_name + ' is not available');
3323 if(this.isVisible() === false) {
3324 if(NETDATA.options.debug.visibility === true || this.debug === true)
3325 this.log('I am not visible');
3330 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3331 if(this.debug === true)
3332 this.log('timed force update detected - allowing this update');
3334 this.current.force_update_at = 0;
3338 if(this.isAutoRefreshable() === true) {
3339 // allow the first update, even if the page is not visible
3340 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3341 if(NETDATA.options.debug.focus === true || this.debug === true)
3342 this.log('canBeAutoRefreshed(): page does not have focus');
3347 if(this.needsRecreation() === true) {
3348 if(this.debug === true)
3349 this.log('canBeAutoRefreshed(): needs re-creation.');
3354 // options valid only for autoRefresh()
3355 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3356 if(NETDATA.globalPanAndZoom.isActive()) {
3357 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3358 if(this.debug === true)
3359 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3364 if(this.debug === true)
3365 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3371 if(this.selected === true) {
3372 if(this.debug === true)
3373 this.log('canBeAutoRefreshed(): I have a selection in place.');
3378 if(this.paused === true) {
3379 if(this.debug === true)
3380 this.log('canBeAutoRefreshed(): I am paused.');
3385 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3386 if(this.debug === true)
3387 this.log('canBeAutoRefreshed(): It is time to update me.');
3397 this.autoRefresh = function(callback) {
3398 if(this.canBeAutoRefreshed() === true && this.running === false) {
3401 state.running = true;
3402 state.updateChart(function() {
3403 state.running = false;
3405 if(typeof callback !== 'undefined')
3410 if(typeof callback !== 'undefined')
3415 this._defaultsFromDownloadedChart = function(chart) {
3417 this.chart_url = chart.url;
3418 this.data_update_every = chart.update_every * 1000;
3419 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3420 this.tm.last_info_downloaded = Date.now();
3422 if(this.title === null)
3423 this.title = chart.title;
3425 if(this.units === null)
3426 this.units = chart.units;
3429 // fetch the chart description from the netdata server
3430 this.getChart = function(callback) {
3431 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3433 this._defaultsFromDownloadedChart(this.chart);
3435 if(typeof callback === 'function')
3439 this.chart_url = "/api/v1/chart?chart=" + this.id;
3441 if(this.debug === true)
3442 this.log('downloading ' + this.chart_url);
3445 url: this.host + this.chart_url,
3448 xhrFields: { withCredentials: true } // required for the cookie
3450 .done(function(chart) {
3451 chart.url = that.chart_url;
3452 that._defaultsFromDownloadedChart(chart);
3453 NETDATA.chartRegistry.add(that.host, that.id, chart);
3456 NETDATA.error(404, that.chart_url);
3457 error('chart not found on url "' + that.chart_url + '"');
3459 .always(function() {
3460 if(typeof callback === 'function')
3466 // ============================================================================================================
3472 NETDATA.resetAllCharts = function(state) {
3473 // first clear the global selection sync
3474 // to make sure no chart is in selected state
3475 state.globalSelectionSyncStop();
3477 // there are 2 possibilities here
3478 // a. state is the global Pan and Zoom master
3479 // b. state is not the global Pan and Zoom master
3481 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3484 // clear the global Pan and Zoom
3485 // this will also refresh the master
3486 // and unblock any charts currently mirroring the master
3487 NETDATA.globalPanAndZoom.clearMaster();
3489 // if we were not the master, reset our status too
3490 // this is required because most probably the mouse
3491 // is over this chart, blocking it from auto-refreshing
3492 if(master === false && (state.paused === true || state.selected === true))
3496 // get or create a chart state, given a DOM element
3497 NETDATA.chartState = function(element) {
3498 var state = $(element).data('netdata-state-object') || null;
3499 if(state === null) {
3500 state = new chartState(element);
3501 $(element).data('netdata-state-object', state);
3506 // ----------------------------------------------------------------------------------------------------------------
3507 // Library functions
3509 // Load a script without jquery
3510 // This is used to load jquery - after it is loaded, we use jquery
3511 NETDATA._loadjQuery = function(callback) {
3512 if(typeof jQuery === 'undefined') {
3513 if(NETDATA.options.debug.main_loop === true)
3514 console.log('loading ' + NETDATA.jQuery);
3516 var script = document.createElement('script');
3517 script.type = 'text/javascript';
3518 script.async = true;
3519 script.src = NETDATA.jQuery;
3521 // script.onabort = onError;
3522 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3523 if(typeof callback === "function")
3524 script.onload = callback;
3526 var s = document.getElementsByTagName('script')[0];
3527 s.parentNode.insertBefore(script, s);
3529 else if(typeof callback === "function")
3533 NETDATA._loadCSS = function(filename) {
3534 // don't use jQuery here
3535 // styles are loaded before jQuery
3536 // to eliminate showing an unstyled page to the user
3538 var fileref = document.createElement("link");
3539 fileref.setAttribute("rel", "stylesheet");
3540 fileref.setAttribute("type", "text/css");
3541 fileref.setAttribute("href", filename);
3543 if (typeof fileref !== 'undefined')
3544 document.getElementsByTagName("head")[0].appendChild(fileref);
3547 NETDATA.colorHex2Rgb = function(hex) {
3548 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3549 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3550 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3551 return r + r + g + g + b + b;
3554 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3556 r: parseInt(result[1], 16),
3557 g: parseInt(result[2], 16),
3558 b: parseInt(result[3], 16)
3562 NETDATA.colorLuminance = function(hex, lum) {
3563 // validate hex string
3564 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3566 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3570 // convert to decimal and change luminosity
3571 var rgb = "#", c, i;
3572 for (i = 0; i < 3; i++) {
3573 c = parseInt(hex.substr(i*2,2), 16);
3574 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3575 rgb += ("00"+c).substr(c.length);
3581 NETDATA.guid = function() {
3583 return Math.floor((1 + Math.random()) * 0x10000)
3588 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3591 NETDATA.zeropad = function(x) {
3592 if(x > -10 && x < 10) return '0' + x.toString();
3593 else return x.toString();
3596 // user function to signal us the DOM has been
3598 NETDATA.updatedDom = function() {
3599 NETDATA.options.updated_dom = true;
3602 NETDATA.ready = function(callback) {
3603 NETDATA.options.pauseCallback = callback;
3606 NETDATA.pause = function(callback) {
3607 if(typeof callback === 'function') {
3608 if (NETDATA.options.pause === true)
3611 NETDATA.options.pauseCallback = callback;
3615 NETDATA.unpause = function() {
3616 NETDATA.options.pauseCallback = null;
3617 NETDATA.options.updated_dom = true;
3618 NETDATA.options.pause = false;
3621 // ----------------------------------------------------------------------------------------------------------------
3623 // this is purely sequential charts refresher
3624 // it is meant to be autonomous
3625 NETDATA.chartRefresherNoParallel = function(index) {
3626 if(NETDATA.options.debug.main_loop === true)
3627 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3629 if(NETDATA.options.updated_dom === true) {
3630 // the dom has been updated
3631 // get the dom parts again
3632 NETDATA.parseDom(NETDATA.chartRefresher);
3635 if(index >= NETDATA.options.targets.length) {
3636 if(NETDATA.options.debug.main_loop === true)
3637 console.log('waiting to restart main loop...');
3639 NETDATA.options.auto_refresher_fast_weight = 0;
3641 setTimeout(function() {
3642 NETDATA.chartRefresher();
3643 }, NETDATA.options.current.idle_between_loops);
3646 var state = NETDATA.options.targets[index];
3648 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3649 if(NETDATA.options.debug.main_loop === true)
3650 console.log('fast rendering...');
3652 state.autoRefresh(function() {
3653 NETDATA.chartRefresherNoParallel(++index);
3657 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3658 NETDATA.options.auto_refresher_fast_weight = 0;
3660 setTimeout(function() {
3661 state.autoRefresh(function() {
3662 NETDATA.chartRefresherNoParallel(++index);
3664 }, NETDATA.options.current.idle_between_charts);
3669 NETDATA.chartRefresherWaitTime = function() {
3670 return NETDATA.options.current.idle_parallel_loops;
3673 // the default refresher
3674 NETDATA.chartRefresher = function() {
3675 // console.log('auto-refresher...');
3677 if(NETDATA.options.pause === true) {
3678 // console.log('auto-refresher is paused');
3679 setTimeout(NETDATA.chartRefresher,
3680 NETDATA.chartRefresherWaitTime());
3684 if(typeof NETDATA.options.pauseCallback === 'function') {
3685 // console.log('auto-refresher is calling pauseCallback');
3686 NETDATA.options.pause = true;
3687 NETDATA.options.pauseCallback();
3688 NETDATA.chartRefresher();
3692 if(NETDATA.options.current.parallel_refresher === false) {
3693 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3694 NETDATA.chartRefresherNoParallel(0);
3698 if(NETDATA.options.updated_dom === true) {
3699 // the dom has been updated
3700 // get the dom parts again
3701 // console.log('auto-refresher is calling parseDom()');
3702 NETDATA.parseDom(NETDATA.chartRefresher);
3707 var targets = NETDATA.options.targets;
3708 var len = targets.length;
3711 state = targets[len];
3712 if(state.isVisible() === false || state.running === true)
3715 if(state.library.initialized === false) {
3716 if(state.library.enabled === true) {
3717 state.library.initialize(NETDATA.chartRefresher);
3721 state.error('chart library "' + state.library_name + '" is not enabled.');
3725 parallel.unshift(state);
3728 if(parallel.length > 0) {
3729 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3730 // this will execute the jobs in parallel
3731 $(parallel).each(function() {
3736 // console.log('auto-refresher nothing to do');
3739 // run the next refresh iteration
3740 setTimeout(NETDATA.chartRefresher,
3741 NETDATA.chartRefresherWaitTime());
3744 NETDATA.parseDom = function(callback) {
3745 NETDATA.options.last_page_scroll = Date.now();
3746 NETDATA.options.updated_dom = false;
3748 var targets = $('div[data-netdata]'); //.filter(':visible');
3750 if(NETDATA.options.debug.main_loop === true)
3751 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3753 NETDATA.options.targets = [];
3754 var len = targets.length;
3756 // the initialization will take care of sizing
3757 // and the "loading..." message
3758 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3761 if(typeof callback === 'function')
3765 // this is the main function - where everything starts
3766 NETDATA.start = function() {
3767 // this should be called only once
3769 NETDATA.options.page_is_visible = true;
3771 $(window).blur(function() {
3772 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3773 NETDATA.options.page_is_visible = false;
3774 if(NETDATA.options.debug.focus === true)
3775 console.log('Lost Focus!');
3779 $(window).focus(function() {
3780 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3781 NETDATA.options.page_is_visible = true;
3782 if(NETDATA.options.debug.focus === true)
3783 console.log('Focus restored!');
3787 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3788 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3789 NETDATA.options.page_is_visible = false;
3790 if(NETDATA.options.debug.focus === true)
3791 console.log('Document has no focus!');
3795 // bootstrap tab switching
3796 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3798 // bootstrap modal switching
3799 var $modal = $('.modal');
3800 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3801 $modal.on('shown.bs.modal', NETDATA.onscroll);
3803 // bootstrap collapse switching
3804 var $collapse = $('.collapse');
3805 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3806 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3808 NETDATA.parseDom(NETDATA.chartRefresher);
3810 // Alarms initialization
3811 setTimeout(NETDATA.alarms.init, 1000);
3813 // Registry initialization
3814 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3816 if(typeof netdataCallback === 'function')
3820 // ----------------------------------------------------------------------------------------------------------------
3823 NETDATA.peityInitialize = function(callback) {
3824 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3826 url: NETDATA.peity_js,
3829 xhrFields: { withCredentials: true } // required for the cookie
3832 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3835 NETDATA.chartLibraries.peity.enabled = false;
3836 NETDATA.error(100, NETDATA.peity_js);
3838 .always(function() {
3839 if(typeof callback === "function")
3844 NETDATA.chartLibraries.peity.enabled = false;
3845 if(typeof callback === "function")
3850 NETDATA.peityChartUpdate = function(state, data) {
3851 state.peity_instance.innerHTML = data.result;
3853 if(state.peity_options.stroke !== state.chartColors()[0]) {
3854 state.peity_options.stroke = state.chartColors()[0];
3855 if(state.chart.chart_type === 'line')
3856 state.peity_options.fill = NETDATA.themes.current.background;
3858 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3861 $(state.peity_instance).peity('line', state.peity_options);
3865 NETDATA.peityChartCreate = function(state, data) {
3866 state.peity_instance = document.createElement('div');
3867 state.element_chart.appendChild(state.peity_instance);
3869 var self = $(state.element);
3870 state.peity_options = {
3871 stroke: NETDATA.themes.current.foreground,
3872 strokeWidth: self.data('peity-strokewidth') || 1,
3873 width: state.chartWidth(),
3874 height: state.chartHeight(),
3875 fill: NETDATA.themes.current.foreground
3878 NETDATA.peityChartUpdate(state, data);
3882 // ----------------------------------------------------------------------------------------------------------------
3885 NETDATA.sparklineInitialize = function(callback) {
3886 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3888 url: NETDATA.sparkline_js,
3891 xhrFields: { withCredentials: true } // required for the cookie
3894 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3897 NETDATA.chartLibraries.sparkline.enabled = false;
3898 NETDATA.error(100, NETDATA.sparkline_js);
3900 .always(function() {
3901 if(typeof callback === "function")
3906 NETDATA.chartLibraries.sparkline.enabled = false;
3907 if(typeof callback === "function")
3912 NETDATA.sparklineChartUpdate = function(state, data) {
3913 state.sparkline_options.width = state.chartWidth();
3914 state.sparkline_options.height = state.chartHeight();
3916 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3920 NETDATA.sparklineChartCreate = function(state, data) {
3921 var self = $(state.element);
3922 var type = self.data('sparkline-type') || 'line';
3923 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3924 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3925 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3926 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3927 var composite = self.data('sparkline-composite') || undefined;
3928 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3929 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3930 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3931 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3932 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3933 var spotColor = self.data('sparkline-spotcolor') || undefined;
3934 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3935 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3936 var spotRadius = self.data('sparkline-spotradius') || undefined;
3937 var valueSpots = self.data('sparkline-valuespots') || undefined;
3938 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3939 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3940 var lineWidth = self.data('sparkline-linewidth') || undefined;
3941 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3942 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3943 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3944 var xvalues = self.data('sparkline-xvalues') || undefined;
3945 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3946 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3947 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3948 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3949 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3950 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3951 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3952 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3953 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3954 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3955 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3956 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3957 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3958 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3959 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3960 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3961 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3962 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3963 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3964 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3965 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3966 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3968 if(spotColor === 'disable') spotColor='';
3969 if(minSpotColor === 'disable') minSpotColor='';
3970 if(maxSpotColor === 'disable') maxSpotColor='';
3972 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3974 state.sparkline_options = {
3976 lineColor: lineColor,
3977 fillColor: fillColor,
3978 chartRangeMin: chartRangeMin,
3979 chartRangeMax: chartRangeMax,
3980 composite: composite,
3981 enableTagOptions: enableTagOptions,
3982 tagOptionPrefix: tagOptionPrefix,
3983 tagValuesAttribute: tagValuesAttribute,
3984 disableHiddenCheck: disableHiddenCheck,
3985 defaultPixelsPerValue: defaultPixelsPerValue,
3986 spotColor: spotColor,
3987 minSpotColor: minSpotColor,
3988 maxSpotColor: maxSpotColor,
3989 spotRadius: spotRadius,
3990 valueSpots: valueSpots,
3991 highlightSpotColor: highlightSpotColor,
3992 highlightLineColor: highlightLineColor,
3993 lineWidth: lineWidth,
3994 normalRangeMin: normalRangeMin,
3995 normalRangeMax: normalRangeMax,
3996 drawNormalOnTop: drawNormalOnTop,
3998 chartRangeClip: chartRangeClip,
3999 chartRangeMinX: chartRangeMinX,
4000 chartRangeMaxX: chartRangeMaxX,
4001 disableInteraction: disableInteraction,
4002 disableTooltips: disableTooltips,
4003 disableHighlight: disableHighlight,
4004 highlightLighten: highlightLighten,
4005 highlightColor: highlightColor,
4006 tooltipContainer: tooltipContainer,
4007 tooltipClassname: tooltipClassname,
4008 tooltipChartTitle: state.title,
4009 tooltipFormat: tooltipFormat,
4010 tooltipPrefix: tooltipPrefix,
4011 tooltipSuffix: tooltipSuffix,
4012 tooltipSkipNull: tooltipSkipNull,
4013 tooltipValueLookups: tooltipValueLookups,
4014 tooltipFormatFieldlist: tooltipFormatFieldlist,
4015 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4016 numberFormatter: numberFormatter,
4017 numberDigitGroupSep: numberDigitGroupSep,
4018 numberDecimalMark: numberDecimalMark,
4019 numberDigitGroupCount: numberDigitGroupCount,
4020 animatedZooms: animatedZooms,
4021 width: state.chartWidth(),
4022 height: state.chartHeight()
4025 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4029 // ----------------------------------------------------------------------------------------------------------------
4036 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4037 if(after < state.netdata_first)
4038 after = state.netdata_first;
4040 if(before > state.netdata_last)
4041 before = state.netdata_last;
4043 state.setMode('zoom');
4044 state.globalSelectionSyncStop();
4045 state.globalSelectionSyncDelay();
4046 state.dygraph_user_action = true;
4047 state.dygraph_force_zoom = true;
4048 state.updateChartPanOrZoom(after, before);
4049 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4052 NETDATA.dygraphSetSelection = function(state, t) {
4053 if(typeof state.dygraph_instance !== 'undefined') {
4054 var r = state.calculateRowForTime(t);
4056 state.dygraph_instance.setSelection(r);
4058 state.dygraph_instance.clearSelection();
4059 state.legendShowUndefined();
4066 NETDATA.dygraphClearSelection = function(state) {
4067 if(typeof state.dygraph_instance !== 'undefined') {
4068 state.dygraph_instance.clearSelection();
4073 NETDATA.dygraphSmoothInitialize = function(callback) {
4075 url: NETDATA.dygraph_smooth_js,
4078 xhrFields: { withCredentials: true } // required for the cookie
4081 NETDATA.dygraph.smooth = true;
4082 smoothPlotter.smoothing = 0.3;
4085 NETDATA.dygraph.smooth = false;
4087 .always(function() {
4088 if(typeof callback === "function")
4093 NETDATA.dygraphInitialize = function(callback) {
4094 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4096 url: NETDATA.dygraph_js,
4099 xhrFields: { withCredentials: true } // required for the cookie
4102 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4105 NETDATA.chartLibraries.dygraph.enabled = false;
4106 NETDATA.error(100, NETDATA.dygraph_js);
4108 .always(function() {
4109 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4110 NETDATA.dygraphSmoothInitialize(callback);
4111 else if(typeof callback === "function")
4116 NETDATA.chartLibraries.dygraph.enabled = false;
4117 if(typeof callback === "function")
4122 NETDATA.dygraphChartUpdate = function(state, data) {
4123 var dygraph = state.dygraph_instance;
4125 if(typeof dygraph === 'undefined')
4126 return NETDATA.dygraphChartCreate(state, data);
4128 // when the chart is not visible, and hidden
4129 // if there is a window resize, dygraph detects
4130 // its element size as 0x0.
4131 // this will make it re-appear properly
4133 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4137 file: data.result.data,
4138 colors: state.chartColors(),
4139 labels: data.result.labels,
4140 labelsDivWidth: state.chartWidth() - 70,
4141 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4144 if(state.dygraph_force_zoom === true) {
4145 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4146 state.log('dygraphChartUpdate() forced zoom update');
4148 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4149 options.isZoomedIgnoreProgrammaticZoom = true;
4150 state.dygraph_force_zoom = false;
4152 else if(state.current.name !== 'auto') {
4153 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4154 state.log('dygraphChartUpdate() loose update');
4157 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4158 state.log('dygraphChartUpdate() strict update');
4160 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4161 options.isZoomedIgnoreProgrammaticZoom = true;
4164 options.valueRange = state.dygraph_options.valueRange;
4166 var oldMax = null, oldMin = null;
4167 if(state.__commonMin !== null) {
4168 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4169 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4171 if(state.__commonMax !== null) {
4172 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4173 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4176 if(state.dygraph_smooth_eligible === true) {
4177 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4178 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4179 NETDATA.dygraphChartCreate(state, data);
4184 dygraph.updateOptions(options);
4187 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4188 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4189 options.valueRange[0] = NETDATA.commonMin.get(state);
4192 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4193 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4194 options.valueRange[1] = NETDATA.commonMax.get(state);
4198 if(redraw === true) {
4199 // state.log('forcing redraw to adapt to common- min/max');
4200 dygraph.updateOptions(options);
4203 state.dygraph_last_rendered = Date.now();
4207 NETDATA.dygraphChartCreate = function(state, data) {
4208 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4209 state.log('dygraphChartCreate()');
4211 var self = $(state.element);
4213 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4214 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4216 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4218 var smooth = (NETDATA.dygraph.smooth === true)
4219 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4222 state.dygraph_options = {
4223 colors: self.data('dygraph-colors') || state.chartColors(),
4225 // leave a few pixels empty on the right of the chart
4226 rightGap: self.data('dygraph-rightgap')
4229 showRangeSelector: self.data('dygraph-showrangeselector')
4232 showRoller: self.data('dygraph-showroller')
4235 title: self.data('dygraph-title')
4238 titleHeight: self.data('dygraph-titleheight')
4241 legend: self.data('dygraph-legend')
4242 || 'always', // we need this to get selection events
4244 labels: data.result.labels,
4246 labelsDiv: self.data('dygraph-labelsdiv')
4247 || state.element_legend_childs.hidden,
4249 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4250 || { 'fontSize':'1px' },
4252 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4253 || state.chartWidth() - 70,
4255 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4258 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4264 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4267 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4270 includeZero: self.data('dygraph-includezero')
4271 || (chart_type === 'stacked'),
4273 xRangePad: self.data('dygraph-xrangepad')
4276 yRangePad: self.data('dygraph-yrangepad')
4279 valueRange: self.data('dygraph-valuerange')
4282 ylabel: state.units,
4284 yLabelWidth: self.data('dygraph-ylabelwidth')
4287 // the function to plot the chart
4290 // The width of the lines connecting data points.
4291 // This can be used to increase the contrast or some graphs.
4292 strokeWidth: self.data('dygraph-strokewidth')
4293 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4295 strokePattern: self.data('dygraph-strokepattern')
4298 // The size of the dot to draw on each point in pixels (see drawPoints).
4299 // A dot is always drawn when a point is "isolated",
4300 // i.e. there is a missing point on either side of it.
4301 // This also controls the size of those dots.
4302 drawPoints: self.data('dygraph-drawpoints')
4305 // Draw points at the edges of gaps in the data.
4306 // This improves visibility of small data segments or other data irregularities.
4307 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4310 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4313 pointSize: self.data('dygraph-pointsize')
4316 // enabling this makes the chart with little square lines
4317 stepPlot: self.data('dygraph-stepplot')
4320 // Draw a border around graph lines to make crossing lines more easily
4321 // distinguishable. Useful for graphs with many lines.
4322 strokeBorderColor: self.data('dygraph-strokebordercolor')
4323 || NETDATA.themes.current.background,
4325 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4326 || (chart_type === 'stacked')?0.0:0.0,
4328 fillGraph: self.data('dygraph-fillgraph')
4329 || (chart_type === 'area' || chart_type === 'stacked'),
4331 fillAlpha: self.data('dygraph-fillalpha')
4332 || ((chart_type === 'stacked')
4333 ?NETDATA.options.current.color_fill_opacity_stacked
4334 :NETDATA.options.current.color_fill_opacity_area),
4336 stackedGraph: self.data('dygraph-stackedgraph')
4337 || (chart_type === 'stacked'),
4339 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4342 drawAxis: self.data('dygraph-drawaxis')
4345 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4348 axisLineColor: self.data('dygraph-axislinecolor')
4349 || NETDATA.themes.current.axis,
4351 axisLineWidth: self.data('dygraph-axislinewidth')
4354 drawGrid: self.data('dygraph-drawgrid')
4357 gridLinePattern: self.data('dygraph-gridlinepattern')
4360 gridLineWidth: self.data('dygraph-gridlinewidth')
4363 gridLineColor: self.data('dygraph-gridlinecolor')
4364 || NETDATA.themes.current.grid,
4366 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4369 sigFigs: self.data('dygraph-sigfigs')
4372 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4375 valueFormatter: self.data('dygraph-valueformatter')
4376 || function(x){ return x.toFixed(2); },
4378 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4379 || highlightCircleSize,
4381 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4382 || null, // TOO SLOW: { strokeWidth: 1.5 },
4384 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4385 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4387 pointClickCallback: self.data('dygraph-pointclickcallback')
4390 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4394 ticker: Dygraph.dateTicker,
4395 axisLabelFormatter: function (d, gran) {
4397 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4399 valueFormatter: function (ms) {
4401 //var d = new Date(ms);
4402 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4404 // no need to return anything here
4411 valueFormatter: function (x) {
4412 // we format legends with the state object
4413 // no need to do anything here
4414 // return (Math.round(x*100) / 100).toLocaleString();
4415 // return state.legendFormatValue(x);
4420 legendFormatter: function(data) {
4421 var elements = state.element_legend_childs;
4423 // if the hidden div is not there
4424 // we are not managing the legend
4425 if(elements.hidden === null) return;
4427 if (typeof data.x !== 'undefined') {
4428 state.legendSetDate(data.x);
4429 var i = data.series.length;
4431 var series = data.series[i];
4432 if(series.isVisible === true)
4433 state.legendSetLabelValue(series.label, series.y);
4435 state.legendSetLabelValue(series.label, null);
4441 drawCallback: function(dygraph, is_initial) {
4442 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4443 state.dygraph_user_action = false;
4445 var x_range = dygraph.xAxisRange();
4446 var after = Math.round(x_range[0]);
4447 var before = Math.round(x_range[1]);
4449 if(NETDATA.options.debug.dygraph === true)
4450 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4452 if(before <= state.netdata_last && after >= state.netdata_first)
4453 state.updateChartPanOrZoom(after, before);
4456 zoomCallback: function(minDate, maxDate, yRanges) {
4459 if(NETDATA.options.debug.dygraph === true)
4460 state.log('dygraphZoomCallback()');
4462 state.globalSelectionSyncStop();
4463 state.globalSelectionSyncDelay();
4464 state.setMode('zoom');
4466 // refresh it to the greatest possible zoom level
4467 state.dygraph_user_action = true;
4468 state.dygraph_force_zoom = true;
4469 state.updateChartPanOrZoom(minDate, maxDate);
4471 highlightCallback: function(event, x, points, row, seriesName) {
4474 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4475 state.log('dygraphHighlightCallback()');
4479 // there is a bug in dygraph when the chart is zoomed enough
4480 // the time it thinks is selected is wrong
4481 // here we calculate the time t based on the row number selected
4483 // var t = state.data_after + row * state.data_update_every;
4484 // 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);
4486 state.globalSelectionSync(x);
4488 // fix legend zIndex using the internal structures of dygraph legend module
4489 // this works, but it is a hack!
4490 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4492 unhighlightCallback: function(event) {
4495 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4496 state.log('dygraphUnhighlightCallback()');
4498 state.unpauseChart();
4499 state.globalSelectionSyncStop();
4501 interactionModel : {
4502 mousedown: function(event, dygraph, context) {
4503 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4504 state.log('interactionModel.mousedown()');
4506 state.dygraph_user_action = true;
4507 state.globalSelectionSyncStop();
4509 if(NETDATA.options.debug.dygraph === true)
4510 state.log('dygraphMouseDown()');
4512 // Right-click should not initiate a zoom.
4513 if(event.button && event.button === 2) return;
4515 context.initializeMouseDown(event, dygraph, context);
4517 if(event.button && event.button === 1) {
4518 if (event.altKey || event.shiftKey) {
4519 state.setMode('pan');
4520 state.globalSelectionSyncDelay();
4521 Dygraph.startPan(event, dygraph, context);
4524 state.setMode('zoom');
4525 state.globalSelectionSyncDelay();
4526 Dygraph.startZoom(event, dygraph, context);
4530 if (event.altKey || event.shiftKey) {
4531 state.setMode('zoom');
4532 state.globalSelectionSyncDelay();
4533 Dygraph.startZoom(event, dygraph, context);
4536 state.setMode('pan');
4537 state.globalSelectionSyncDelay();
4538 Dygraph.startPan(event, dygraph, context);
4542 mousemove: function(event, dygraph, context) {
4543 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4544 state.log('interactionModel.mousemove()');
4546 if(context.isPanning) {
4547 state.dygraph_user_action = true;
4548 state.globalSelectionSyncStop();
4549 state.globalSelectionSyncDelay();
4550 state.setMode('pan');
4551 context.is2DPan = false;
4552 Dygraph.movePan(event, dygraph, context);
4554 else if(context.isZooming) {
4555 state.dygraph_user_action = true;
4556 state.globalSelectionSyncStop();
4557 state.globalSelectionSyncDelay();
4558 state.setMode('zoom');
4559 Dygraph.moveZoom(event, dygraph, context);
4562 mouseup: function(event, dygraph, context) {
4563 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4564 state.log('interactionModel.mouseup()');
4566 if (context.isPanning) {
4567 state.dygraph_user_action = true;
4568 state.globalSelectionSyncDelay();
4569 Dygraph.endPan(event, dygraph, context);
4571 else if (context.isZooming) {
4572 state.dygraph_user_action = true;
4573 state.globalSelectionSyncDelay();
4574 Dygraph.endZoom(event, dygraph, context);
4577 click: function(event, dygraph, context) {
4581 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4582 state.log('interactionModel.click()');
4584 event.preventDefault();
4586 dblclick: function(event, dygraph, context) {
4591 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4592 state.log('interactionModel.dblclick()');
4593 NETDATA.resetAllCharts(state);
4595 wheel: function(event, dygraph, context) {
4598 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4599 state.log('interactionModel.wheel()');
4601 // Take the offset of a mouse event on the dygraph canvas and
4602 // convert it to a pair of percentages from the bottom left.
4603 // (Not top left, bottom is where the lower value is.)
4604 function offsetToPercentage(g, offsetX, offsetY) {
4605 // This is calculating the pixel offset of the leftmost date.
4606 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4607 var yar0 = g.yAxisRange(0);
4609 // This is calculating the pixel of the highest value. (Top pixel)
4610 var yOffset = g.toDomCoords(null, yar0[1])[1];
4612 // x y w and h are relative to the corner of the drawing area,
4613 // so that the upper corner of the drawing area is (0, 0).
4614 var x = offsetX - xOffset;
4615 var y = offsetY - yOffset;
4617 // This is computing the rightmost pixel, effectively defining the
4619 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4621 // This is computing the lowest pixel, effectively defining the height.
4622 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4624 // Percentage from the left.
4625 var xPct = w === 0 ? 0 : (x / w);
4626 // Percentage from the top.
4627 var yPct = h === 0 ? 0 : (y / h);
4629 // The (1-) part below changes it from "% distance down from the top"
4630 // to "% distance up from the bottom".
4631 return [xPct, (1-yPct)];
4634 // Adjusts [x, y] toward each other by zoomInPercentage%
4635 // Split it so the left/bottom axis gets xBias/yBias of that change and
4636 // tight/top gets (1-xBias)/(1-yBias) of that change.
4638 // If a bias is missing it splits it down the middle.
4639 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4640 xBias = xBias || 0.5;
4641 yBias = yBias || 0.5;
4643 function adjustAxis(axis, zoomInPercentage, bias) {
4644 var delta = axis[1] - axis[0];
4645 var increment = delta * zoomInPercentage;
4646 var foo = [increment * bias, increment * (1-bias)];
4648 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4651 var yAxes = g.yAxisRanges();
4653 for (var i = 0; i < yAxes.length; i++) {
4654 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4657 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4660 if(event.altKey || event.shiftKey) {
4661 state.dygraph_user_action = true;
4663 state.globalSelectionSyncStop();
4664 state.globalSelectionSyncDelay();
4666 // http://dygraphs.com/gallery/interaction-api.js
4668 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4670 normal_def = event.wheelDelta / 40;
4673 normal_def = event.deltaY * -1.2;
4675 var normal = (event.detail) ? event.detail * -1 : normal_def;
4676 var percentage = normal / 50;
4678 if (!(event.offsetX && event.offsetY)){
4679 event.offsetX = event.layerX - event.target.offsetLeft;
4680 event.offsetY = event.layerY - event.target.offsetTop;
4683 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4684 var xPct = percentages[0];
4685 var yPct = percentages[1];
4687 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4688 var after = new_x_range[0];
4689 var before = new_x_range[1];
4691 var first = state.netdata_first + state.data_update_every;
4692 var last = state.netdata_last + state.data_update_every;
4695 after -= (before - last);
4702 state.setMode('zoom');
4703 if(state.updateChartPanOrZoom(after, before) === true)
4704 dygraph.updateOptions({ dateWindow: [ after, before ] });
4706 event.preventDefault();
4709 touchstart: function(event, dygraph, context) {
4710 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4711 state.log('interactionModel.touchstart()');
4713 state.dygraph_user_action = true;
4714 state.setMode('zoom');
4717 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4719 // we overwrite the touch directions at the end, to overwrite
4720 // the internal default of dygraph
4721 context.touchDirections = { x: true, y: false };
4723 state.dygraph_last_touch_start = Date.now();
4724 state.dygraph_last_touch_move = 0;
4726 if(typeof event.touches[0].pageX === 'number')
4727 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4729 state.dygraph_last_touch_page_x = 0;
4731 touchmove: function(event, dygraph, context) {
4732 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4733 state.log('interactionModel.touchmove()');
4735 state.dygraph_user_action = true;
4736 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4738 state.dygraph_last_touch_move = Date.now();
4740 touchend: function(event, dygraph, context) {
4741 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4742 state.log('interactionModel.touchend()');
4744 state.dygraph_user_action = true;
4745 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4747 // if it didn't move, it is a selection
4748 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4749 // internal api of dygraph
4750 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4751 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4752 if(NETDATA.dygraphSetSelection(state, t) === true)
4753 state.globalSelectionSync(t);
4756 // if it was double tap within double click time, reset the charts
4757 var now = Date.now();
4758 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4759 if(state.dygraph_last_touch_move === 0) {
4760 var dt = now - state.dygraph_last_touch_end;
4761 if(dt <= NETDATA.options.current.double_click_speed)
4762 NETDATA.resetAllCharts(state);
4766 // remember the timestamp of the last touch end
4767 state.dygraph_last_touch_end = now;
4772 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4773 state.dygraph_options.drawGrid = false;
4774 state.dygraph_options.drawAxis = false;
4775 state.dygraph_options.title = undefined;
4776 state.dygraph_options.ylabel = undefined;
4777 state.dygraph_options.yLabelWidth = 0;
4778 state.dygraph_options.labelsDivWidth = 120;
4779 state.dygraph_options.labelsDivStyles.width = '120px';
4780 state.dygraph_options.labelsSeparateLines = true;
4781 state.dygraph_options.rightGap = 0;
4782 state.dygraph_options.yRangePad = 1;
4785 if(smooth === true) {
4786 state.dygraph_smooth_eligible = true;
4788 if(NETDATA.options.current.smooth_plot === true)
4789 state.dygraph_options.plotter = smoothPlotter;
4791 else state.dygraph_smooth_eligible = false;
4793 state.dygraph_instance = new Dygraph(state.element_chart,
4794 data.result.data, state.dygraph_options);
4796 state.dygraph_force_zoom = false;
4797 state.dygraph_user_action = false;
4798 state.dygraph_last_rendered = Date.now();
4800 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4801 state.__commonMin = self.data('common-min') || null;
4802 state.__commonMax = self.data('common-max') || null;
4805 state.log('incompatible version of Dygraph detected');
4806 state.__commonMin = null;
4807 state.__commonMax = null;
4813 // ----------------------------------------------------------------------------------------------------------------
4816 NETDATA.morrisInitialize = function(callback) {
4817 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4819 // morris requires raphael
4820 if(!NETDATA.chartLibraries.raphael.initialized) {
4821 if(NETDATA.chartLibraries.raphael.enabled) {
4822 NETDATA.raphaelInitialize(function() {
4823 NETDATA.morrisInitialize(callback);
4827 NETDATA.chartLibraries.morris.enabled = false;
4828 if(typeof callback === "function")
4833 NETDATA._loadCSS(NETDATA.morris_css);
4836 url: NETDATA.morris_js,
4839 xhrFields: { withCredentials: true } // required for the cookie
4842 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4845 NETDATA.chartLibraries.morris.enabled = false;
4846 NETDATA.error(100, NETDATA.morris_js);
4848 .always(function() {
4849 if(typeof callback === "function")
4855 NETDATA.chartLibraries.morris.enabled = false;
4856 if(typeof callback === "function")
4861 NETDATA.morrisChartUpdate = function(state, data) {
4862 state.morris_instance.setData(data.result.data);
4866 NETDATA.morrisChartCreate = function(state, data) {
4868 state.morris_options = {
4869 element: state.element_chart.id,
4870 data: data.result.data,
4872 ykeys: data.dimension_names,
4873 labels: data.dimension_names,
4879 continuousLine: false,
4880 behaveLikeLine: false
4883 if(state.chart.chart_type === 'line')
4884 state.morris_instance = new Morris.Line(state.morris_options);
4886 else if(state.chart.chart_type === 'area') {
4887 state.morris_options.behaveLikeLine = true;
4888 state.morris_instance = new Morris.Area(state.morris_options);
4891 state.morris_instance = new Morris.Area(state.morris_options);
4896 // ----------------------------------------------------------------------------------------------------------------
4899 NETDATA.raphaelInitialize = function(callback) {
4900 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4902 url: NETDATA.raphael_js,
4905 xhrFields: { withCredentials: true } // required for the cookie
4908 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4911 NETDATA.chartLibraries.raphael.enabled = false;
4912 NETDATA.error(100, NETDATA.raphael_js);
4914 .always(function() {
4915 if(typeof callback === "function")
4920 NETDATA.chartLibraries.raphael.enabled = false;
4921 if(typeof callback === "function")
4926 NETDATA.raphaelChartUpdate = function(state, data) {
4927 $(state.element_chart).raphael(data.result, {
4928 width: state.chartWidth(),
4929 height: state.chartHeight()
4935 NETDATA.raphaelChartCreate = function(state, data) {
4936 $(state.element_chart).raphael(data.result, {
4937 width: state.chartWidth(),
4938 height: state.chartHeight()
4944 // ----------------------------------------------------------------------------------------------------------------
4947 NETDATA.c3Initialize = function(callback) {
4948 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4951 if(!NETDATA.chartLibraries.d3.initialized) {
4952 if(NETDATA.chartLibraries.d3.enabled) {
4953 NETDATA.d3Initialize(function() {
4954 NETDATA.c3Initialize(callback);
4958 NETDATA.chartLibraries.c3.enabled = false;
4959 if(typeof callback === "function")
4964 NETDATA._loadCSS(NETDATA.c3_css);
4970 xhrFields: { withCredentials: true } // required for the cookie
4973 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4976 NETDATA.chartLibraries.c3.enabled = false;
4977 NETDATA.error(100, NETDATA.c3_js);
4979 .always(function() {
4980 if(typeof callback === "function")
4986 NETDATA.chartLibraries.c3.enabled = false;
4987 if(typeof callback === "function")
4992 NETDATA.c3ChartUpdate = function(state, data) {
4993 state.c3_instance.destroy();
4994 return NETDATA.c3ChartCreate(state, data);
4996 //state.c3_instance.load({
4997 // rows: data.result,
5004 NETDATA.c3ChartCreate = function(state, data) {
5006 state.element_chart.id = 'c3-' + state.uuid;
5007 // console.log('id = ' + state.element_chart.id);
5009 state.c3_instance = c3.generate({
5010 bindto: '#' + state.element_chart.id,
5012 width: state.chartWidth(),
5013 height: state.chartHeight()
5016 pattern: state.chartColors()
5021 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5027 format: function(x) {
5028 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5055 // console.log(state.c3_instance);
5060 // ----------------------------------------------------------------------------------------------------------------
5063 NETDATA.d3Initialize = function(callback) {
5064 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5069 xhrFields: { withCredentials: true } // required for the cookie
5072 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5075 NETDATA.chartLibraries.d3.enabled = false;
5076 NETDATA.error(100, NETDATA.d3_js);
5078 .always(function() {
5079 if(typeof callback === "function")
5084 NETDATA.chartLibraries.d3.enabled = false;
5085 if(typeof callback === "function")
5090 NETDATA.d3ChartUpdate = function(state, data) {
5097 NETDATA.d3ChartCreate = function(state, data) {
5104 // ----------------------------------------------------------------------------------------------------------------
5107 NETDATA.googleInitialize = function(callback) {
5108 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5110 url: NETDATA.google_js,
5113 xhrFields: { withCredentials: true } // required for the cookie
5116 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5117 google.load('visualization', '1.1', {
5118 'packages': ['corechart', 'controls'],
5119 'callback': callback
5123 NETDATA.chartLibraries.google.enabled = false;
5124 NETDATA.error(100, NETDATA.google_js);
5125 if(typeof callback === "function")
5130 NETDATA.chartLibraries.google.enabled = false;
5131 if(typeof callback === "function")
5136 NETDATA.googleChartUpdate = function(state, data) {
5137 var datatable = new google.visualization.DataTable(data.result);
5138 state.google_instance.draw(datatable, state.google_options);
5142 NETDATA.googleChartCreate = function(state, data) {
5143 var datatable = new google.visualization.DataTable(data.result);
5145 state.google_options = {
5146 colors: state.chartColors(),
5148 // do not set width, height - the chart resizes itself
5149 //width: state.chartWidth(),
5150 //height: state.chartHeight(),
5155 // title: "Time of Day",
5156 // format:'HH:mm:ss',
5157 viewWindowMode: 'maximized',
5169 viewWindowMode: 'pretty',
5184 focusTarget: 'category',
5191 titlePosition: 'out',
5202 curveType: 'function',
5207 switch(state.chart.chart_type) {
5209 state.google_options.vAxis.viewWindowMode = 'maximized';
5210 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5211 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5215 state.google_options.isStacked = true;
5216 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5217 state.google_options.vAxis.viewWindowMode = 'maximized';
5218 state.google_options.vAxis.minValue = null;
5219 state.google_options.vAxis.maxValue = null;
5220 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5225 state.google_options.lineWidth = 2;
5226 state.google_instance = new google.visualization.LineChart(state.element_chart);
5230 state.google_instance.draw(datatable, state.google_options);
5234 // ----------------------------------------------------------------------------------------------------------------
5236 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5237 if(typeof value !== 'number') value = 0;
5238 if(typeof min !== 'number') min = 0;
5239 if(typeof max !== 'number') max = 0;
5241 if(min > value) min = value;
5242 if(max < value) max = value;
5244 // make sure it is zero based
5245 if(min > 0) min = 0;
5246 if(max < 0) max = 0;
5251 pcent = Math.round(value * 100 / max);
5252 if(pcent === 0) pcent = 0.1;
5256 pcent = Math.round(-value * 100 / min);
5257 if(pcent === 0) pcent = -0.1;
5263 // ----------------------------------------------------------------------------------------------------------------
5266 NETDATA.easypiechartInitialize = function(callback) {
5267 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5269 url: NETDATA.easypiechart_js,
5272 xhrFields: { withCredentials: true } // required for the cookie
5275 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5278 NETDATA.chartLibraries.easypiechart.enabled = false;
5279 NETDATA.error(100, NETDATA.easypiechart_js);
5281 .always(function() {
5282 if(typeof callback === "function")
5287 NETDATA.chartLibraries.easypiechart.enabled = false;
5288 if(typeof callback === "function")
5293 NETDATA.easypiechartClearSelection = function(state) {
5294 if(typeof state.easyPieChartEvent !== 'undefined') {
5295 if(state.easyPieChartEvent.timer !== undefined) {
5296 clearTimeout(state.easyPieChartEvent.timer);
5299 state.easyPieChartEvent.timer = undefined;
5302 if(state.isAutoRefreshable() === true && state.data !== null) {
5303 NETDATA.easypiechartChartUpdate(state, state.data);
5306 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5307 state.easyPieChart_instance.update(0);
5309 state.easyPieChart_instance.enableAnimation();
5314 NETDATA.easypiechartSetSelection = function(state, t) {
5315 if(state.timeIsVisible(t) !== true)
5316 return NETDATA.easypiechartClearSelection(state);
5318 var slot = state.calculateRowForTime(t);
5319 if(slot < 0 || slot >= state.data.result.length)
5320 return NETDATA.easypiechartClearSelection(state);
5322 if(typeof state.easyPieChartEvent === 'undefined') {
5323 state.easyPieChartEvent = {
5330 var value = state.data.result[state.data.result.length - 1 - slot];
5331 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5332 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5333 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5335 state.easyPieChartEvent.value = value;
5336 state.easyPieChartEvent.pcent = pcent;
5337 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5339 if(state.easyPieChartEvent.timer === undefined) {
5340 state.easyPieChart_instance.disableAnimation();
5342 state.easyPieChartEvent.timer = setTimeout(function() {
5343 state.easyPieChartEvent.timer = undefined;
5344 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5345 }, NETDATA.options.current.charts_selection_animation_delay);
5351 NETDATA.easypiechartChartUpdate = function(state, data) {
5352 var value, min, max, pcent;
5354 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5359 value = data.result[0];
5360 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5361 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5362 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5365 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5366 state.easyPieChart_instance.update(pcent);
5370 NETDATA.easypiechartChartCreate = function(state, data) {
5371 var self = $(state.element);
5372 var chart = $(state.element_chart);
5374 var value = data.result[0];
5375 var min = self.data('easypiechart-min-value') || null;
5376 var max = self.data('easypiechart-max-value') || null;
5377 var adjust = self.data('easypiechart-adjust') || null;
5380 min = NETDATA.commonMin.get(state);
5381 state.easyPieChartMin = null;
5384 state.easyPieChartMin = min;
5387 max = NETDATA.commonMax.get(state);
5388 state.easyPieChartMax = null;
5391 state.easyPieChartMax = max;
5393 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5395 chart.data('data-percent', pcent);
5399 case 'width': size = state.chartHeight(); break;
5400 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5401 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5403 default: size = state.chartWidth(); break;
5405 state.element.style.width = size + 'px';
5406 state.element.style.height = size + 'px';
5408 var stroke = Math.floor(size / 22);
5409 if(stroke < 3) stroke = 2;
5411 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5412 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5413 state.easyPieChartLabel = document.createElement('span');
5414 state.easyPieChartLabel.className = 'easyPieChartLabel';
5415 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5416 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5417 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5418 state.element_chart.appendChild(state.easyPieChartLabel);
5420 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5421 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5422 state.easyPieChartTitle = document.createElement('span');
5423 state.easyPieChartTitle.className = 'easyPieChartTitle';
5424 state.easyPieChartTitle.innerText = state.title;
5425 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5426 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5427 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5428 state.element_chart.appendChild(state.easyPieChartTitle);
5430 var unitfontsize = Math.round(titlefontsize * 0.9);
5431 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5432 state.easyPieChartUnits = document.createElement('span');
5433 state.easyPieChartUnits.className = 'easyPieChartUnits';
5434 state.easyPieChartUnits.innerText = state.units;
5435 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5436 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5437 state.element_chart.appendChild(state.easyPieChartUnits);
5439 var barColor = self.data('easypiechart-barcolor');
5440 if(typeof barColor === 'undefined' || barColor === null)
5441 barColor = state.chartColors()[0];
5443 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5444 var tmp = eval(barColor);
5445 if(typeof tmp === 'function')
5449 chart.easyPieChart({
5451 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5452 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5453 scaleLength: self.data('easypiechart-scalelength') || 5,
5454 lineCap: self.data('easypiechart-linecap') || 'round',
5455 lineWidth: self.data('easypiechart-linewidth') || stroke,
5456 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5457 size: self.data('easypiechart-size') || size,
5458 rotate: self.data('easypiechart-rotate') || 0,
5459 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5460 easing: self.data('easypiechart-easing') || undefined
5463 // when we just re-create the chart
5464 // do not animate the first update
5466 if(typeof state.easyPieChart_instance !== 'undefined')
5469 state.easyPieChart_instance = chart.data('easyPieChart');
5470 if(animate === false) state.easyPieChart_instance.disableAnimation();
5471 state.easyPieChart_instance.update(pcent);
5472 if(animate === false) state.easyPieChart_instance.enableAnimation();
5476 // ----------------------------------------------------------------------------------------------------------------
5479 NETDATA.gaugeInitialize = function(callback) {
5480 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5482 url: NETDATA.gauge_js,
5485 xhrFields: { withCredentials: true } // required for the cookie
5488 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5491 NETDATA.chartLibraries.gauge.enabled = false;
5492 NETDATA.error(100, NETDATA.gauge_js);
5494 .always(function() {
5495 if(typeof callback === "function")
5500 NETDATA.chartLibraries.gauge.enabled = false;
5501 if(typeof callback === "function")
5506 NETDATA.gaugeAnimation = function(state, status) {
5509 if(typeof status === 'boolean' && status === false)
5511 else if(typeof status === 'number')
5514 // console.log('gauge speed ' + speed);
5515 state.gauge_instance.animationSpeed = speed;
5516 state.___gaugeOld__.speed = speed;
5519 NETDATA.gaugeSet = function(state, value, min, max) {
5520 if(typeof value !== 'number') value = 0;
5521 if(typeof min !== 'number') min = 0;
5522 if(typeof max !== 'number') max = 0;
5523 if(value > max) max = value;
5524 if(value < min) min = value;
5530 else if(min === max)
5533 // gauge.js has an issue if the needle
5534 // is smaller than min or larger than max
5535 // when we set the new values
5536 // the needle will go crazy
5538 // to prevent it, we always feed it
5539 // with a percentage, so that the needle
5540 // is always between min and max
5541 var pcent = (value - min) * 100 / (max - min);
5543 // bug fix for gauge.js 1.3.1
5544 // if the value is the absolute min or max, the chart is broken
5545 if(pcent < 0.001) pcent = 0.001;
5546 if(pcent > 99.999) pcent = 99.999;
5548 state.gauge_instance.set(pcent);
5549 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5551 state.___gaugeOld__.value = value;
5552 state.___gaugeOld__.min = min;
5553 state.___gaugeOld__.max = max;
5556 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5557 if(state.___gaugeOld__.valueLabel !== value) {
5558 state.___gaugeOld__.valueLabel = value;
5559 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5561 if(state.___gaugeOld__.minLabel !== min) {
5562 state.___gaugeOld__.minLabel = min;
5563 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5565 if(state.___gaugeOld__.maxLabel !== max) {
5566 state.___gaugeOld__.maxLabel = max;
5567 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5571 NETDATA.gaugeClearSelection = function(state) {
5572 if(typeof state.gaugeEvent !== 'undefined') {
5573 if(state.gaugeEvent.timer !== undefined) {
5574 clearTimeout(state.gaugeEvent.timer);
5577 state.gaugeEvent.timer = undefined;
5580 if(state.isAutoRefreshable() === true && state.data !== null) {
5581 NETDATA.gaugeChartUpdate(state, state.data);
5584 NETDATA.gaugeAnimation(state, false);
5585 NETDATA.gaugeSet(state, null, null, null);
5586 NETDATA.gaugeSetLabels(state, null, null, null);
5589 NETDATA.gaugeAnimation(state, true);
5593 NETDATA.gaugeSetSelection = function(state, t) {
5594 if(state.timeIsVisible(t) !== true)
5595 return NETDATA.gaugeClearSelection(state);
5597 var slot = state.calculateRowForTime(t);
5598 if(slot < 0 || slot >= state.data.result.length)
5599 return NETDATA.gaugeClearSelection(state);
5601 if(typeof state.gaugeEvent === 'undefined') {
5602 state.gaugeEvent = {
5610 var value = state.data.result[state.data.result.length - 1 - slot];
5611 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5612 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5614 // make sure it is zero based
5615 if(min > 0) min = 0;
5616 if(max < 0) max = 0;
5618 state.gaugeEvent.value = value;
5619 state.gaugeEvent.min = min;
5620 state.gaugeEvent.max = max;
5621 NETDATA.gaugeSetLabels(state, value, min, max);
5623 if(state.gaugeEvent.timer === undefined) {
5624 NETDATA.gaugeAnimation(state, false);
5626 state.gaugeEvent.timer = setTimeout(function() {
5627 state.gaugeEvent.timer = undefined;
5628 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5629 }, NETDATA.options.current.charts_selection_animation_delay);
5635 NETDATA.gaugeChartUpdate = function(state, data) {
5636 var value, min, max;
5638 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5642 NETDATA.gaugeSetLabels(state, null, null, null);
5645 value = data.result[0];
5646 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5647 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5648 if(value < min) min = value;
5649 if(value > max) max = value;
5651 // make sure it is zero based
5652 if(min > 0) min = 0;
5653 if(max < 0) max = 0;
5655 NETDATA.gaugeSetLabels(state, value, min, max);
5658 NETDATA.gaugeSet(state, value, min, max);
5662 NETDATA.gaugeChartCreate = function(state, data) {
5663 var self = $(state.element);
5664 // var chart = $(state.element_chart);
5666 var value = data.result[0];
5667 var min = self.data('gauge-min-value') || null;
5668 var max = self.data('gauge-max-value') || null;
5669 var adjust = self.data('gauge-adjust') || null;
5670 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5671 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5672 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5673 var stopColor = self.data('gauge-stop-color') || void 0;
5674 var generateGradient = self.data('gauge-generate-gradient') || false;
5677 min = NETDATA.commonMin.get(state);
5678 state.gaugeMin = null;
5681 state.gaugeMin = min;
5684 max = NETDATA.commonMax.get(state);
5685 state.gaugeMax = null;
5688 state.gaugeMax = max;
5690 // make sure it is zero based
5691 if(min > 0) min = 0;
5692 if(max < 0) max = 0;
5694 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5696 // case 'width': width = height * ratio; break;
5698 // default: height = width / ratio; break;
5700 //state.element.style.width = width.toString() + 'px';
5701 //state.element.style.height = height.toString() + 'px';
5706 lines: 12, // The number of lines to draw
5707 angle: 0.15, // The span of the gauge arc
5708 lineWidth: 0.50, // The line thickness
5709 radiusScale: 0.85, // Relative radius
5711 length: 0.8, // 0.9 The radius of the inner circle
5712 strokeWidth: 0.035, // The rotation offset
5713 color: pointerColor // Fill color
5715 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5716 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5717 colorStart: startColor, // Colors
5718 colorStop: stopColor, // just experiment with them
5719 strokeColor: strokeColor, // to see which ones work best for you
5720 generateGradient: (generateGradient === true),
5722 highDpiSupport: true // High resolution support
5725 if (generateGradient.constructor === Array) {
5727 // data-gauge-generate-gradient="[0, 50, 100]"
5728 // data-gauge-gradient-percent-color-0="#FFFFFF"
5729 // data-gauge-gradient-percent-color-50="#999900"
5730 // data-gauge-gradient-percent-color-100="#000000"
5732 options.percentColors = [];
5733 var len = generateGradient.length;
5735 var pcent = generateGradient[len];
5736 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5737 if(color !== false) {
5741 options.percentColors.unshift(a);
5744 if(options.percentColors.length === 0)
5745 delete options.percentColors;
5747 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5748 //noinspection PointlessArithmeticExpressionJS
5749 options.percentColors = [
5750 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5751 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5752 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5753 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5754 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5755 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5756 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5757 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5758 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5759 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5760 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5763 state.gauge_canvas = document.createElement('canvas');
5764 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5765 state.gauge_canvas.className = 'gaugeChart';
5766 state.gauge_canvas.width = width;
5767 state.gauge_canvas.height = height;
5768 state.element_chart.appendChild(state.gauge_canvas);
5770 var valuefontsize = Math.floor(height / 6);
5771 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5772 state.gaugeChartLabel = document.createElement('span');
5773 state.gaugeChartLabel.className = 'gaugeChartLabel';
5774 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5775 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5776 state.element_chart.appendChild(state.gaugeChartLabel);
5778 var titlefontsize = Math.round(valuefontsize / 2);
5780 state.gaugeChartTitle = document.createElement('span');
5781 state.gaugeChartTitle.className = 'gaugeChartTitle';
5782 state.gaugeChartTitle.innerText = state.title;
5783 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5784 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5785 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5786 state.element_chart.appendChild(state.gaugeChartTitle);
5788 var unitfontsize = Math.round(titlefontsize * 0.9);
5789 state.gaugeChartUnits = document.createElement('span');
5790 state.gaugeChartUnits.className = 'gaugeChartUnits';
5791 state.gaugeChartUnits.innerText = state.units;
5792 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5793 state.element_chart.appendChild(state.gaugeChartUnits);
5795 state.gaugeChartMin = document.createElement('span');
5796 state.gaugeChartMin.className = 'gaugeChartMin';
5797 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5798 state.element_chart.appendChild(state.gaugeChartMin);
5800 state.gaugeChartMax = document.createElement('span');
5801 state.gaugeChartMax.className = 'gaugeChartMax';
5802 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5803 state.element_chart.appendChild(state.gaugeChartMax);
5805 // when we just re-create the chart
5806 // do not animate the first update
5808 if(typeof state.gauge_instance !== 'undefined')
5811 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5813 state.___gaugeOld__ = {
5822 // we will always feed a percentage
5823 state.gauge_instance.minValue = 0;
5824 state.gauge_instance.maxValue = 100;
5826 NETDATA.gaugeAnimation(state, animate);
5827 NETDATA.gaugeSet(state, value, min, max);
5828 NETDATA.gaugeSetLabels(state, value, min, max);
5829 NETDATA.gaugeAnimation(state, true);
5833 // ----------------------------------------------------------------------------------------------------------------
5834 // Charts Libraries Registration
5836 NETDATA.chartLibraries = {
5838 initialize: NETDATA.dygraphInitialize,
5839 create: NETDATA.dygraphChartCreate,
5840 update: NETDATA.dygraphChartUpdate,
5841 resize: function(state) {
5842 if(typeof state.dygraph_instance.resize === 'function')
5843 state.dygraph_instance.resize();
5845 setSelection: NETDATA.dygraphSetSelection,
5846 clearSelection: NETDATA.dygraphClearSelection,
5847 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5850 format: function(state) { void(state); return 'json'; },
5851 options: function(state) { void(state); return 'ms|flip'; },
5852 legend: function(state) {
5853 return (this.isSparkline(state) === false)?'right-side':null;
5855 autoresize: function(state) { void(state); return true; },
5856 max_updates_to_recreate: function(state) { void(state); return 5000; },
5857 track_colors: function(state) { void(state); return true; },
5858 pixels_per_point: function(state) {
5859 return (this.isSparkline(state) === false)?3:2;
5861 isSparkline: function(state) {
5862 if(typeof state.dygraph_sparkline === 'undefined') {
5863 var t = $(state.element).data('dygraph-theme');
5864 state.dygraph_sparkline = (t === 'sparkline');
5866 return state.dygraph_sparkline;
5870 initialize: NETDATA.sparklineInitialize,
5871 create: NETDATA.sparklineChartCreate,
5872 update: NETDATA.sparklineChartUpdate,
5874 setSelection: undefined, // function(state, t) { void(state); return true; },
5875 clearSelection: undefined, // function(state) { void(state); return true; },
5876 toolboxPanAndZoom: null,
5879 format: function(state) { void(state); return 'array'; },
5880 options: function(state) { void(state); return 'flip|abs'; },
5881 legend: function(state) { void(state); return null; },
5882 autoresize: function(state) { void(state); return false; },
5883 max_updates_to_recreate: function(state) { void(state); return 5000; },
5884 track_colors: function(state) { void(state); return false; },
5885 pixels_per_point: function(state) { void(state); return 3; }
5888 initialize: NETDATA.peityInitialize,
5889 create: NETDATA.peityChartCreate,
5890 update: NETDATA.peityChartUpdate,
5892 setSelection: undefined, // function(state, t) { void(state); return true; },
5893 clearSelection: undefined, // function(state) { void(state); return true; },
5894 toolboxPanAndZoom: null,
5897 format: function(state) { void(state); return 'ssvcomma'; },
5898 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5899 legend: function(state) { void(state); return null; },
5900 autoresize: function(state) { void(state); return false; },
5901 max_updates_to_recreate: function(state) { void(state); return 5000; },
5902 track_colors: function(state) { void(state); return false; },
5903 pixels_per_point: function(state) { void(state); return 3; }
5906 initialize: NETDATA.morrisInitialize,
5907 create: NETDATA.morrisChartCreate,
5908 update: NETDATA.morrisChartUpdate,
5910 setSelection: undefined, // function(state, t) { void(state); return true; },
5911 clearSelection: undefined, // function(state) { void(state); return true; },
5912 toolboxPanAndZoom: null,
5915 format: function(state) { void(state); return 'json'; },
5916 options: function(state) { void(state); return 'objectrows|ms'; },
5917 legend: function(state) { void(state); return null; },
5918 autoresize: function(state) { void(state); return false; },
5919 max_updates_to_recreate: function(state) { void(state); return 50; },
5920 track_colors: function(state) { void(state); return false; },
5921 pixels_per_point: function(state) { void(state); return 15; }
5924 initialize: NETDATA.googleInitialize,
5925 create: NETDATA.googleChartCreate,
5926 update: NETDATA.googleChartUpdate,
5928 setSelection: undefined, //function(state, t) { void(state); return true; },
5929 clearSelection: undefined, //function(state) { void(state); return true; },
5930 toolboxPanAndZoom: null,
5933 format: function(state) { void(state); return 'datatable'; },
5934 options: function(state) { void(state); return ''; },
5935 legend: function(state) { void(state); return null; },
5936 autoresize: function(state) { void(state); return false; },
5937 max_updates_to_recreate: function(state) { void(state); return 300; },
5938 track_colors: function(state) { void(state); return false; },
5939 pixels_per_point: function(state) { void(state); return 4; }
5942 initialize: NETDATA.raphaelInitialize,
5943 create: NETDATA.raphaelChartCreate,
5944 update: NETDATA.raphaelChartUpdate,
5946 setSelection: undefined, // function(state, t) { void(state); return true; },
5947 clearSelection: undefined, // function(state) { void(state); return true; },
5948 toolboxPanAndZoom: null,
5951 format: function(state) { void(state); return 'json'; },
5952 options: function(state) { void(state); return ''; },
5953 legend: function(state) { void(state); return null; },
5954 autoresize: function(state) { void(state); return false; },
5955 max_updates_to_recreate: function(state) { void(state); return 5000; },
5956 track_colors: function(state) { void(state); return false; },
5957 pixels_per_point: function(state) { void(state); return 3; }
5960 initialize: NETDATA.c3Initialize,
5961 create: NETDATA.c3ChartCreate,
5962 update: NETDATA.c3ChartUpdate,
5964 setSelection: undefined, // function(state, t) { void(state); return true; },
5965 clearSelection: undefined, // function(state) { void(state); return true; },
5966 toolboxPanAndZoom: null,
5969 format: function(state) { void(state); return 'csvjsonarray'; },
5970 options: function(state) { void(state); return 'milliseconds'; },
5971 legend: function(state) { void(state); return null; },
5972 autoresize: function(state) { void(state); return false; },
5973 max_updates_to_recreate: function(state) { void(state); return 5000; },
5974 track_colors: function(state) { void(state); return false; },
5975 pixels_per_point: function(state) { void(state); return 15; }
5978 initialize: NETDATA.d3Initialize,
5979 create: NETDATA.d3ChartCreate,
5980 update: NETDATA.d3ChartUpdate,
5982 setSelection: undefined, // function(state, t) { void(state); return true; },
5983 clearSelection: undefined, // function(state) { void(state); return true; },
5984 toolboxPanAndZoom: null,
5987 format: function(state) { void(state); return 'json'; },
5988 options: function(state) { void(state); return ''; },
5989 legend: function(state) { void(state); return null; },
5990 autoresize: function(state) { void(state); return false; },
5991 max_updates_to_recreate: function(state) { void(state); return 5000; },
5992 track_colors: function(state) { void(state); return false; },
5993 pixels_per_point: function(state) { void(state); return 3; }
5996 initialize: NETDATA.easypiechartInitialize,
5997 create: NETDATA.easypiechartChartCreate,
5998 update: NETDATA.easypiechartChartUpdate,
6000 setSelection: NETDATA.easypiechartSetSelection,
6001 clearSelection: NETDATA.easypiechartClearSelection,
6002 toolboxPanAndZoom: null,
6005 format: function(state) { void(state); return 'array'; },
6006 options: function(state) { void(state); return 'absolute'; },
6007 legend: function(state) { void(state); return null; },
6008 autoresize: function(state) { void(state); return false; },
6009 max_updates_to_recreate: function(state) { void(state); return 5000; },
6010 track_colors: function(state) { void(state); return true; },
6011 pixels_per_point: function(state) { void(state); return 3; },
6015 initialize: NETDATA.gaugeInitialize,
6016 create: NETDATA.gaugeChartCreate,
6017 update: NETDATA.gaugeChartUpdate,
6019 setSelection: NETDATA.gaugeSetSelection,
6020 clearSelection: NETDATA.gaugeClearSelection,
6021 toolboxPanAndZoom: null,
6024 format: function(state) { void(state); return 'array'; },
6025 options: function(state) { void(state); return 'absolute'; },
6026 legend: function(state) { void(state); return null; },
6027 autoresize: function(state) { void(state); return false; },
6028 max_updates_to_recreate: function(state) { void(state); return 5000; },
6029 track_colors: function(state) { void(state); return true; },
6030 pixels_per_point: function(state) { void(state); return 3; },
6035 NETDATA.registerChartLibrary = function(library, url) {
6036 if(NETDATA.options.debug.libraries === true)
6037 console.log("registering chart library: " + library);
6039 NETDATA.chartLibraries[library].url = url;
6040 NETDATA.chartLibraries[library].initialized = true;
6041 NETDATA.chartLibraries[library].enabled = true;
6044 // ----------------------------------------------------------------------------------------------------------------
6045 // Load required JS libraries and CSS
6047 NETDATA.requiredJs = [
6049 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6051 isAlreadyLoaded: function() {
6052 // check if bootstrap is loaded
6053 if(typeof $().emulateTransitionEnd === 'function')
6056 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6061 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6062 isAlreadyLoaded: function() { return false; }
6066 NETDATA.requiredCSS = [
6068 url: NETDATA.themes.current.bootstrap_css,
6069 isAlreadyLoaded: function() {
6070 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6074 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6075 isAlreadyLoaded: function() { return false; }
6078 url: NETDATA.themes.current.dashboard_css,
6079 isAlreadyLoaded: function() { return false; }
6083 NETDATA.loadedRequiredJs = 0;
6084 NETDATA.loadRequiredJs = function(index, callback) {
6085 if(index >= NETDATA.requiredJs.length) {
6086 if(typeof callback === 'function')
6091 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6092 NETDATA.loadedRequiredJs++;
6093 NETDATA.loadRequiredJs(++index, callback);
6097 if(NETDATA.options.debug.main_loop === true)
6098 console.log('loading ' + NETDATA.requiredJs[index].url);
6101 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6105 url: NETDATA.requiredJs[index].url,
6108 xhrFields: { withCredentials: true } // required for the cookie
6111 if(NETDATA.options.debug.main_loop === true)
6112 console.log('loaded ' + NETDATA.requiredJs[index].url);
6115 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6117 .always(function() {
6118 NETDATA.loadedRequiredJs++;
6121 NETDATA.loadRequiredJs(++index, callback);
6125 NETDATA.loadRequiredJs(++index, callback);
6128 NETDATA.loadRequiredCSS = function(index) {
6129 if(index >= NETDATA.requiredCSS.length)
6132 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6133 NETDATA.loadRequiredCSS(++index);
6137 if(NETDATA.options.debug.main_loop === true)
6138 console.log('loading ' + NETDATA.requiredCSS[index].url);
6140 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6141 NETDATA.loadRequiredCSS(++index);
6145 // ----------------------------------------------------------------------------------------------------------------
6146 // Registry of netdata hosts
6149 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6150 chart_div_offset: 100, // give that space above the chart when scrolling to it
6151 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6152 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6154 ms_penalty: 0, // the time penalty of the next alarm
6155 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6156 // if alarms are shown faster than: one per 500ms
6158 notifications: false, // when true, the browser supports notifications (may not be granted though)
6159 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6160 first_notification_id: 0, // the id of the first alarm_log entry for this session
6161 // this is used to prevent CLEAR notifications for past events
6162 // notifications_shown: [],
6164 server: null, // the server to connect to for fetching alarms
6165 current: null, // the list of raised alarms - updated in the background
6166 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6168 notify: function(entry) {
6169 // console.log('alarm ' + entry.unique_id);
6171 if(entry.updated === true) {
6172 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6176 var value_string = entry.value_string;
6178 if(NETDATA.alarms.current !== null) {
6179 // get the current value_string
6180 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6181 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6182 value_string = t.value_string;
6185 var name = entry.name.replace(/_/g, ' ');
6186 var status = entry.status.toLowerCase();
6187 var title = name + ' = ' + value_string.toString();
6188 var tag = entry.alarm_id;
6189 var icon = 'images/seo-performance-128.png';
6190 var interaction = false;
6194 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6196 switch(entry.status) {
6204 case 'UNINITIALIZED':
6208 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6209 // console.log('alarm ' + entry.unique_id + ' is not current');
6212 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6213 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6216 if(entry.no_clear_notification === true) {
6217 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6220 title = name + ' back to normal (' + value_string.toString() + ')';
6221 icon = 'images/check-mark-2-128-green.png';
6222 interaction = false;
6226 if(entry.old_status === 'CRITICAL')
6227 status = 'demoted to ' + entry.status.toLowerCase();
6229 icon = 'images/alert-128-orange.png';
6230 interaction = false;
6234 if(entry.old_status === 'WARNING')
6235 status = 'escalated to ' + entry.status.toLowerCase();
6237 icon = 'images/alert-128-red.png';
6242 console.log('invalid alarm status ' + entry.status);
6247 // cleanup old notifications with the same alarm_id as this one
6248 // FIXME: it does not seem to work on any web browser!
6249 var len = NETDATA.alarms.notifications_shown.length;
6251 var n = NETDATA.alarms.notifications_shown[len];
6252 if(n.data.alarm_id === entry.alarm_id) {
6253 console.log('removing old alarm ' + n.data.unique_id);
6255 // close the notification
6258 // remove it from the array
6259 NETDATA.alarms.notifications_shown.splice(len, 1);
6260 len = NETDATA.alarms.notifications_shown.length;
6267 setTimeout(function() {
6268 // show this notification
6269 // console.log('new notification: ' + title);
6270 var n = new Notification(title, {
6271 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6273 requireInteraction: interaction,
6274 icon: NETDATA.serverDefault + icon,
6278 n.onclick = function(event) {
6279 event.preventDefault();
6280 NETDATA.alarms.onclick(event.target.data);
6284 // NETDATA.alarms.notifications_shown.push(n);
6285 // console.log(entry);
6286 }, NETDATA.alarms.ms_penalty);
6288 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6292 scrollToChart: function(chart_id) {
6293 if(typeof chart_id === 'string') {
6294 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6295 if(typeof offset !== 'undefined') {
6296 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6303 scrollToAlarm: function(alarm) {
6304 if(typeof alarm === 'object') {
6305 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6307 if(ret === true && NETDATA.options.page_is_visible === false)
6309 // 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.');
6314 notifyAll: function() {
6315 // console.log('FETCHING ALARM LOG');
6316 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6317 // console.log('ALARM LOG FETCHED');
6319 if(data === null || typeof data !== 'object') {
6320 console.log('invalid alarms log response');
6324 if(data.length === 0) {
6325 console.log('received empty alarm log');
6329 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6331 data.sort(function(a, b) {
6332 if(a.unique_id > b.unique_id) return -1;
6333 if(a.unique_id < b.unique_id) return 1;
6337 NETDATA.alarms.ms_penalty = 0;
6339 var len = data.length;
6341 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6342 NETDATA.alarms.notify(data[len]);
6345 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6348 NETDATA.alarms.last_notification_id = data[0].unique_id;
6349 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6350 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6354 check_notifications: function() {
6355 // returns true if we should fire 1+ notifications
6357 if(NETDATA.alarms.notifications !== true) {
6358 // console.log('notifications not available');
6362 if(Notification.permission !== 'granted') {
6363 // console.log('notifications not granted');
6367 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6368 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6370 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6371 // console.log('new alarms detected');
6374 //else console.log('no new alarms');
6376 // else console.log('cannot process alarms');
6381 get: function(what, callback) {
6383 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6387 'Cache-Control': 'no-cache, no-store',
6388 'Pragma': 'no-cache'
6390 xhrFields: { withCredentials: true } // required for the cookie
6392 .done(function(data) {
6393 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6394 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6396 if(typeof callback === 'function')
6397 return callback(data);
6400 NETDATA.error(415, NETDATA.alarms.server);
6402 if(typeof callback === 'function')
6403 return callback(null);
6407 update_forever: function() {
6408 NETDATA.alarms.get('active', function(data) {
6410 NETDATA.alarms.current = data;
6412 if(NETDATA.alarms.check_notifications() === true) {
6413 NETDATA.alarms.notifyAll();
6416 if (typeof NETDATA.alarms.callback === 'function') {
6417 NETDATA.alarms.callback(data);
6420 // Health monitoring is disabled on this netdata
6421 if(data.status === false) return;
6424 setTimeout(NETDATA.alarms.update_forever, 10000);
6428 get_log: function(last_id, callback) {
6429 // console.log('fetching all log after ' + last_id.toString());
6431 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6435 'Cache-Control': 'no-cache, no-store',
6436 'Pragma': 'no-cache'
6438 xhrFields: { withCredentials: true } // required for the cookie
6440 .done(function(data) {
6441 if(typeof callback === 'function')
6442 return callback(data);
6445 NETDATA.error(416, NETDATA.alarms.server);
6447 if(typeof callback === 'function')
6448 return callback(null);
6453 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6455 NETDATA.alarms.last_notification_id =
6456 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6458 if(NETDATA.alarms.onclick === null)
6459 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6461 if(netdataShowAlarms === true) {
6462 NETDATA.alarms.update_forever();
6464 if('Notification' in window) {
6465 // console.log('notifications available');
6466 NETDATA.alarms.notifications = true;
6468 if(Notification.permission === 'default')
6469 Notification.requestPermission();
6475 // ----------------------------------------------------------------------------------------------------------------
6476 // Registry of netdata hosts
6478 NETDATA.registry = {
6479 server: null, // the netdata registry server
6480 person_guid: null, // the unique ID of this browser / user
6481 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6482 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6483 machines: null, // the user's other URLs
6484 machines_array: null, // the user's other URLs in an array
6487 parsePersonUrls: function(person_urls) {
6488 // console.log(person_urls);
6489 NETDATA.registry.person_urls = person_urls;
6492 NETDATA.registry.machines = {};
6493 NETDATA.registry.machines_array = [];
6495 var apu = person_urls;
6498 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6499 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6505 accesses: apu[i][3],
6509 obj.alternate_urls.push(apu[i][1]);
6511 NETDATA.registry.machines[apu[i][0]] = obj;
6512 NETDATA.registry.machines_array.push(obj);
6515 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6517 var pu = NETDATA.registry.machines[apu[i][0]];
6518 if(pu.last_t < apu[i][2]) {
6520 pu.last_t = apu[i][2];
6521 pu.name = apu[i][4];
6523 pu.accesses += apu[i][3];
6524 pu.alternate_urls.push(apu[i][1]);
6529 if(typeof netdataRegistryCallback === 'function')
6530 netdataRegistryCallback(NETDATA.registry.machines_array);
6534 if(netdataRegistry !== true) return;
6536 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6538 NETDATA.registry.server = data.registry;
6539 NETDATA.registry.machine_guid = data.machine_guid;
6540 NETDATA.registry.hostname = data.hostname;
6542 NETDATA.registry.access(2, function (person_urls) {
6543 NETDATA.registry.parsePersonUrls(person_urls);
6550 hello: function(host, callback) {
6551 host = NETDATA.fixHost(host);
6553 // send HELLO to a netdata server:
6554 // 1. verifies the server is reachable
6555 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6557 url: host + '/api/v1/registry?action=hello',
6561 'Cache-Control': 'no-cache, no-store',
6562 'Pragma': 'no-cache'
6564 xhrFields: { withCredentials: true } // required for the cookie
6566 .done(function(data) {
6567 if(typeof data.status !== 'string' || data.status !== 'ok') {
6568 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6572 if(typeof callback === 'function')
6573 return callback(data);
6576 NETDATA.error(407, host);
6578 if(typeof callback === 'function')
6579 return callback(null);
6583 access: function(max_redirects, callback) {
6584 // send ACCESS to a netdata registry:
6585 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6586 // 2. it responds with a list of netdata servers we know
6587 // the registry identifies us using a cookie it sets the first time we access it
6588 // the registry may respond with a redirect URL to send us to another registry
6590 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),
6594 'Cache-Control': 'no-cache, no-store',
6595 'Pragma': 'no-cache'
6597 xhrFields: { withCredentials: true } // required for the cookie
6599 .done(function(data) {
6600 var redirect = null;
6601 if(typeof data.registry === 'string')
6602 redirect = data.registry;
6604 if(typeof data.status !== 'string' || data.status !== 'ok') {
6605 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6610 if(redirect !== null && max_redirects > 0) {
6611 NETDATA.registry.server = redirect;
6612 NETDATA.registry.access(max_redirects - 1, callback);
6615 if(typeof callback === 'function')
6616 return callback(null);
6620 if(typeof data.person_guid === 'string')
6621 NETDATA.registry.person_guid = data.person_guid;
6623 if(typeof callback === 'function')
6624 return callback(data.urls);
6628 NETDATA.error(410, NETDATA.registry.server);
6630 if(typeof callback === 'function')
6631 return callback(null);
6635 delete: function(delete_url, callback) {
6636 // send DELETE to a netdata registry:
6638 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),
6642 'Cache-Control': 'no-cache, no-store',
6643 'Pragma': 'no-cache'
6645 xhrFields: { withCredentials: true } // required for the cookie
6647 .done(function(data) {
6648 if(typeof data.status !== 'string' || data.status !== 'ok') {
6649 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6653 if(typeof callback === 'function')
6654 return callback(data);
6657 NETDATA.error(412, NETDATA.registry.server);
6659 if(typeof callback === 'function')
6660 return callback(null);
6664 search: function(machine_guid, callback) {
6665 // SEARCH for the URLs of a machine:
6667 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,
6671 'Cache-Control': 'no-cache, no-store',
6672 'Pragma': 'no-cache'
6674 xhrFields: { withCredentials: true } // required for the cookie
6676 .done(function(data) {
6677 if(typeof data.status !== 'string' || data.status !== 'ok') {
6678 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6682 if(typeof callback === 'function')
6683 return callback(data);
6686 NETDATA.error(418, NETDATA.registry.server);
6688 if(typeof callback === 'function')
6689 return callback(null);
6693 switch: function(new_person_guid, callback) {
6696 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,
6700 'Cache-Control': 'no-cache, no-store',
6701 'Pragma': 'no-cache'
6703 xhrFields: { withCredentials: true } // required for the cookie
6705 .done(function(data) {
6706 if(typeof data.status !== 'string' || data.status !== 'ok') {
6707 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6711 if(typeof callback === 'function')
6712 return callback(data);
6715 NETDATA.error(414, NETDATA.registry.server);
6717 if(typeof callback === 'function')
6718 return callback(null);
6723 // ----------------------------------------------------------------------------------------------------------------
6726 if(typeof netdataPrepCallback === 'function')
6727 netdataPrepCallback();
6729 NETDATA.errorReset();
6730 NETDATA.loadRequiredCSS(0);
6732 NETDATA._loadjQuery(function() {
6733 NETDATA.loadRequiredJs(0, function() {
6734 if(typeof $().emulateTransitionEnd !== 'function') {
6735 // bootstrap is not available
6736 NETDATA.options.current.show_help = false;
6739 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6740 if(NETDATA.options.debug.main_loop === true)
6741 console.log('starting chart refresh thread');
6747 })(window, document);