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 return isSlowDeviceResult;
244 // ----------------------------------------------------------------------------------------------------------------
245 // the defaults for all charts
247 // if the user does not specify any of these, the following will be used
249 NETDATA.chartDefaults = {
250 host: NETDATA.serverDefault, // the server to get data from
251 width: '100%', // the chart width - can be null
252 height: '100%', // the chart height - can be null
253 min_width: null, // the chart minimum width - can be null
254 library: 'dygraph', // the graphing library to use
255 method: 'average', // the grouping method
256 before: 0, // panning
257 after: -600, // panning
258 pixels_per_point: 1, // the detail of the chart
259 fill_luminance: 0.8 // luminance of colors in solid areas
262 // ----------------------------------------------------------------------------------------------------------------
266 pauseCallback: null, // a callback when we are really paused
268 pause: false, // when enabled we don't auto-refresh the charts
270 targets: null, // an array of all the state objects that are
271 // currently active (independently of their
272 // viewport visibility)
274 updated_dom: true, // when true, the DOM has been updated with
275 // new elements we have to check.
277 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
278 // rendering charts continuously.
279 // used with .current.fast_render_timeframe
281 page_is_visible: true, // when true, this page is visible
283 auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
284 // auto-refresher for some time (when a chart is
285 // performing pan or zoom, we need to stop refreshing
286 // all other charts, to have the maximum speed for
287 // rendering the chart that is panned or zoomed).
288 // Used with .current.global_pan_sync_time
290 last_resized: Date.now(), // the timestamp of the last resize request
292 last_page_scroll: 0, // the timestamp the last time the page was scrolled
294 // the current profile
295 // we may have many...
297 pixels_per_point: isSlowDevice()?5:1, // the minimum pixels per point for all charts
298 // increase this to speed javascript up
299 // each chart library has its own limit too
300 // the max of this and the chart library is used
301 // the final is calculated every time, so a change
302 // here will have immediate effect on the next chart
305 idle_between_charts: 100, // ms - how much time to wait between chart updates
307 fast_render_timeframe: 200, // ms - render continuously until this time of continuous
308 // rendering has been reached
309 // this setting is used to make it render e.g. 10
310 // charts at once, sleep idle_between_charts time
311 // and continue for another 10 charts.
313 idle_between_loops: 500, // ms - if all charts have been updated, wait this
314 // time before starting again.
316 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
318 idle_lost_focus: 500, // ms - when the window does not have focus, check
319 // if focus has been regained, every this time
321 global_pan_sync_time: 1000, // ms - when you pan or zoom a chart, the background
322 // auto-refreshing of charts is paused for this amount
325 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
326 // of time before setting up synchronized selections
329 sync_selection: true, // enable or disable selection sync
331 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
333 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
335 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
337 update_only_visible: true, // enable or disable visibility management
339 parallel_refresher: (isSlowDevice() === false), // enable parallel refresh of charts
341 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
343 destroy_on_hide: (isSlowDevice() === true), // destroy charts when they are not visible
345 show_help: netdataShowHelp, // when enabled the charts will show some help
346 show_help_delay_show_ms: 500,
347 show_help_delay_hide_ms: 0,
349 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
351 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
352 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
354 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
356 smooth_plot: (isSlowDevice() === false), // enable smooth plot, where possible
358 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
360 color_fill_opacity_line: 1.0,
361 color_fill_opacity_area: 0.2,
362 color_fill_opacity_stacked: 0.8,
364 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
365 pan_and_zoom_factor_multiplier_control: 2.0,
366 pan_and_zoom_factor_multiplier_shift: 3.0,
367 pan_and_zoom_factor_multiplier_alt: 4.0,
369 abort_ajax_on_scroll: false, // kill pending ajax page scroll
370 async_on_scroll: false, // sync/async onscroll handler
371 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
373 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
375 setOptionCallback: function() { }
383 chart_data_url: false,
384 chart_errors: false, // FIXME: remember to set it to false before merging
392 NETDATA.statistics = {
395 refreshes_active_max: 0
399 // ----------------------------------------------------------------------------------------------------------------
400 // local storage options
402 NETDATA.localStorage = {
405 callback: {} // only used for resetting back to defaults
408 NETDATA.localStorageTested = -1;
409 NETDATA.localStorageTest = function() {
410 if(NETDATA.localStorageTested !== -1)
411 return NETDATA.localStorageTested;
413 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
416 localStorage.setItem(test, test);
417 localStorage.removeItem(test);
418 NETDATA.localStorageTested = true;
421 NETDATA.localStorageTested = false;
425 NETDATA.localStorageTested = false;
427 return NETDATA.localStorageTested;
430 NETDATA.localStorageGet = function(key, def, callback) {
433 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
434 NETDATA.localStorage.default[key.toString()] = def;
435 NETDATA.localStorage.callback[key.toString()] = callback;
438 if(NETDATA.localStorageTest() === true) {
440 // console.log('localStorage: loading "' + key.toString() + '"');
441 ret = localStorage.getItem(key.toString());
442 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
443 if(ret === null || ret === 'undefined') {
444 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
445 localStorage.setItem(key.toString(), JSON.stringify(def));
449 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
450 ret = JSON.parse(ret);
451 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
455 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
460 if(typeof ret === 'undefined' || ret === 'undefined') {
461 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
465 NETDATA.localStorage.current[key.toString()] = ret;
469 NETDATA.localStorageSet = function(key, value, callback) {
470 if(typeof value === 'undefined' || value === 'undefined') {
471 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
474 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
475 NETDATA.localStorage.default[key.toString()] = value;
476 NETDATA.localStorage.current[key.toString()] = value;
477 NETDATA.localStorage.callback[key.toString()] = callback;
480 if(NETDATA.localStorageTest() === true) {
481 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
483 localStorage.setItem(key.toString(), JSON.stringify(value));
486 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
490 NETDATA.localStorage.current[key.toString()] = value;
494 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
495 var keys = Object.keys(obj);
496 var len = keys.length;
500 if(typeof obj[i] === 'object') {
501 //console.log('object ' + prefix + '.' + i.toString());
502 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
506 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
510 NETDATA.setOption = function(key, value) {
511 if(key.toString() === 'setOptionCallback') {
512 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
513 NETDATA.options.current[key.toString()] = value;
514 NETDATA.options.current.setOptionCallback();
517 else if(NETDATA.options.current[key.toString()] !== value) {
518 var name = 'options.' + key.toString();
520 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
521 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
523 //console.log(NETDATA.localStorage);
524 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
525 //console.log(NETDATA.options);
526 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
528 if(typeof NETDATA.options.current.setOptionCallback === 'function')
529 NETDATA.options.current.setOptionCallback();
535 NETDATA.getOption = function(key) {
536 return NETDATA.options.current[key.toString()];
539 // read settings from local storage
540 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
542 // always start with this option enabled.
543 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
545 NETDATA.resetOptions = function() {
546 var keys = Object.keys(NETDATA.localStorage.default);
547 var len = keys.length;
550 var a = i.split('.');
552 if(a[0] === 'options') {
553 if(a[1] === 'setOptionCallback') continue;
554 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
555 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
557 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
559 else if(a[0] === 'chart_heights') {
560 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
561 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
567 // ----------------------------------------------------------------------------------------------------------------
569 if(NETDATA.options.debug.main_loop === true)
570 console.log('welcome to NETDATA');
572 NETDATA.onresizeCallback = null;
573 NETDATA.onresize = function() {
574 NETDATA.options.last_resized = Date.now();
577 if(typeof NETDATA.onresizeCallback === 'function')
578 NETDATA.onresizeCallback();
581 NETDATA.onscroll_updater_count = 0;
582 NETDATA.onscroll_updater_running = false;
583 NETDATA.onscroll_updater_last_run = 0;
584 NETDATA.onscroll_updater_watchdog = null;
585 NETDATA.onscroll_updater_max_duration = 0;
586 NETDATA.onscroll_updater_above_threshold_count = 0;
587 NETDATA.onscroll_updater = function() {
588 NETDATA.onscroll_updater_running = true;
589 NETDATA.onscroll_updater_count++;
590 var start = Date.now();
592 var targets = NETDATA.options.targets;
593 var len = targets.length;
595 // when the user scrolls he sees that we have
596 // hidden all the not-visible charts
597 // using this little function we try to switch
598 // the charts back to visible quickly
601 if(NETDATA.options.abort_ajax_on_scroll === true) {
602 // we have to cancel pending requests too
605 if (targets[len]._updating === true) {
606 if (typeof targets[len].xhr !== 'undefined') {
607 targets[len].xhr.abort();
608 targets[len].running = false;
609 targets[len]._updating = false;
611 targets[len].isVisible();
616 // just find which chart is visible
619 targets[len].isVisible();
622 var end = Date.now();
623 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
625 if(NETDATA.options.current.async_on_scroll === false) {
626 var dt = end - start;
627 if(dt > NETDATA.onscroll_updater_max_duration) {
628 // console.log('max onscroll event handler duration increased to ' + dt);
629 NETDATA.onscroll_updater_max_duration = dt;
632 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
633 // console.log('slow: ' + dt);
634 NETDATA.onscroll_updater_above_threshold_count++;
636 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
637 NETDATA.setOption('async_on_scroll', true);
638 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
643 NETDATA.onscroll_updater_last_run = start;
644 NETDATA.onscroll_updater_running = false;
647 NETDATA.onscroll = function() {
648 // console.log('onscroll');
650 NETDATA.options.last_page_scroll = Date.now();
651 NETDATA.options.auto_refresher_stop_until = 0;
653 if(NETDATA.options.targets === null) return;
655 if(NETDATA.options.current.async_on_scroll === true) {
657 if(NETDATA.onscroll_updater_running === false) {
658 NETDATA.onscroll_updater_running = true;
659 setTimeout(NETDATA.onscroll_updater, 0);
662 if(NETDATA.onscroll_updater_watchdog !== null)
663 clearTimeout(NETDATA.onscroll_updater_watchdog);
665 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
666 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
667 // console.log('watchdog');
668 NETDATA.onscroll_updater();
671 NETDATA.onscroll_updater_watchdog = null;
677 NETDATA.onscroll_updater();
681 window.onresize = NETDATA.onresize;
682 window.onscroll = NETDATA.onscroll;
684 // ----------------------------------------------------------------------------------------------------------------
687 NETDATA.errorCodes = {
688 100: { message: "Cannot load chart library", alert: true },
689 101: { message: "Cannot load jQuery", alert: true },
690 402: { message: "Chart library not found", alert: false },
691 403: { message: "Chart library not enabled/is failed", alert: false },
692 404: { message: "Chart not found", alert: false },
693 405: { message: "Cannot download charts index from server", alert: true },
694 406: { message: "Invalid charts index downloaded from server", alert: true },
695 407: { message: "Cannot HELLO netdata server", alert: false },
696 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
697 409: { message: "Cannot ACCESS netdata registry", alert: false },
698 410: { message: "Netdata registry ACCESS failed", alert: false },
699 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
700 412: { message: "Netdata registry DELETE failed", alert: false },
701 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
702 414: { message: "Netdata registry SWITCH failed", alert: false },
703 415: { message: "Netdata alarms download failed", alert: false },
704 416: { message: "Netdata alarms log download failed", alert: false },
705 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
706 418: { message: "Netdata registry SEARCH failed", alert: false }
708 NETDATA.errorLast = {
714 NETDATA.error = function(code, msg) {
715 NETDATA.errorLast.code = code;
716 NETDATA.errorLast.message = msg;
717 NETDATA.errorLast.datetime = Date.now();
719 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
722 if(typeof netdataErrorCallback === 'function') {
723 ret = netdataErrorCallback('system', code, msg);
726 if(ret && NETDATA.errorCodes[code].alert)
727 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
730 NETDATA.errorReset = function() {
731 NETDATA.errorLast.code = 0;
732 NETDATA.errorLast.message = "You are doing fine!";
733 NETDATA.errorLast.datetime = 0;
736 // ----------------------------------------------------------------------------------------------------------------
737 // commonMin & commonMax
739 NETDATA.commonMin = {
743 get: function(state) {
744 if(typeof state.__commonMin === 'undefined') {
745 // get the commonMin setting
746 var self = $(state.element);
747 state.__commonMin = self.data('common-min') || null;
750 var min = state.data.min;
751 var name = state.__commonMin;
754 // we don't need commonMin
755 //state.log('no need for commonMin');
759 var t = this.keys[name];
760 if(typeof t === 'undefined') {
762 this.keys[name] = {};
766 var uuid = state.uuid;
767 if(typeof t[uuid] !== 'undefined') {
768 if(t[uuid] === min) {
769 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
770 return this.latest[name];
772 else if(min < this.latest[name]) {
773 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
775 this.latest[name] = min;
783 // find the common min
786 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
788 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
789 this.latest[name] = m;
794 NETDATA.commonMax = {
798 get: function(state) {
799 if(typeof state.__commonMax === 'undefined') {
800 // get the commonMax setting
801 var self = $(state.element);
802 state.__commonMax = self.data('common-max') || null;
805 var max = state.data.max;
806 var name = state.__commonMax;
809 // we don't need commonMax
810 //state.log('no need for commonMax');
814 var t = this.keys[name];
815 if(typeof t === 'undefined') {
817 this.keys[name] = {};
821 var uuid = state.uuid;
822 if(typeof t[uuid] !== 'undefined') {
823 if(t[uuid] === max) {
824 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
825 return this.latest[name];
827 else if(max > this.latest[name]) {
828 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
830 this.latest[name] = max;
838 // find the common max
841 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
843 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
844 this.latest[name] = m;
849 // ----------------------------------------------------------------------------------------------------------------
852 // When multiple charts need the same chart, we avoid downloading it
853 // multiple times (and having it in browser memory multiple time)
854 // by using this registry.
856 // Every time we download a chart definition, we save it here with .add()
857 // Then we try to get it back with .get(). If that fails, we download it.
859 NETDATA.fixHost = function(host) {
860 while(host.slice(-1) === '/')
861 host = host.substring(0, host.length - 1);
866 NETDATA.chartRegistry = {
869 fixid: function(id) {
870 return id.replace(/:/g, "_").replace(/\//g, "_");
873 add: function(host, id, data) {
874 host = this.fixid(host);
877 if(typeof this.charts[host] === 'undefined')
878 this.charts[host] = {};
880 //console.log('added ' + host + '/' + id);
881 this.charts[host][id] = data;
884 get: function(host, id) {
885 host = this.fixid(host);
888 if(typeof this.charts[host] === 'undefined')
891 if(typeof this.charts[host][id] === 'undefined')
894 //console.log('cached ' + host + '/' + id);
895 return this.charts[host][id];
898 downloadAll: function(host, callback) {
899 host = NETDATA.fixHost(host);
904 url: host + '/api/v1/charts',
907 xhrFields: { withCredentials: true } // required for the cookie
909 .done(function(data) {
911 var h = NETDATA.chartRegistry.fixid(host);
912 self.charts[h] = data.charts;
914 else NETDATA.error(406, host + '/api/v1/charts');
916 if(typeof callback === 'function')
917 return callback(data);
920 NETDATA.error(405, host + '/api/v1/charts');
922 if(typeof callback === 'function')
923 return callback(null);
928 // ----------------------------------------------------------------------------------------------------------------
929 // Global Pan and Zoom on charts
931 // Using this structure are synchronize all the charts, so that
932 // when you pan or zoom one, all others are automatically refreshed
933 // to the same timespan.
935 NETDATA.globalPanAndZoom = {
936 seq: 0, // timestamp ms
937 // every time a chart is panned or zoomed
938 // we set the timestamp here
939 // then we use it as a sequence number
940 // to find if other charts are synchronized
941 // to this time-range
943 master: null, // the master chart (state), to which all others
946 force_before_ms: null, // the timespan to sync all other charts
947 force_after_ms: null,
952 setMaster: function(state, after, before) {
953 if(NETDATA.options.current.sync_pan_and_zoom === false)
956 if(this.master !== null && this.master !== state)
957 this.master.resetChart(true, true);
959 var now = Date.now();
962 this.force_after_ms = after;
963 this.force_before_ms = before;
964 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
966 if(typeof this.callback === 'function')
967 this.callback(true, after, before);
971 clearMaster: function() {
972 if(this.master !== null) {
973 var st = this.master;
980 this.force_after_ms = null;
981 this.force_before_ms = null;
982 NETDATA.options.auto_refresher_stop_until = 0;
984 if(typeof this.callback === 'function')
985 this.callback(false, 0, 0);
988 // is the given state the master of the global
989 // pan and zoom sync?
990 isMaster: function(state) {
991 return (this.master === state);
994 // are we currently have a global pan and zoom sync?
995 isActive: function() {
996 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
999 // check if a chart, other than the master
1000 // needs to be refreshed, due to the global pan and zoom
1001 shouldBeAutoRefreshed: function(state) {
1002 if(this.master === null || this.seq === 0)
1005 //if(state.needsRecreation())
1008 return (state.tm.pan_and_zoom_seq !== this.seq);
1012 // ----------------------------------------------------------------------------------------------------------------
1013 // dimensions selection
1016 // move color assignment to dimensions, here
1018 var dimensionStatus = function(parent, label, name_div, value_div, color) {
1019 this.enabled = false;
1020 this.parent = parent;
1022 this.name_div = null;
1023 this.value_div = null;
1024 this.color = NETDATA.themes.current.foreground;
1025 this.selected = (parent.unselected_count === 0);
1027 this.setOptions(name_div, value_div, color);
1030 dimensionStatus.prototype.invalidate = function() {
1031 this.name_div = null;
1032 this.value_div = null;
1033 this.enabled = false;
1036 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1039 if(this.name_div !== name_div) {
1040 this.name_div = name_div;
1041 this.name_div.title = this.label;
1042 this.name_div.style.color = this.color;
1043 if(this.selected === false)
1044 this.name_div.className = 'netdata-legend-name not-selected';
1046 this.name_div.className = 'netdata-legend-name selected';
1049 if(this.value_div !== value_div) {
1050 this.value_div = value_div;
1051 this.value_div.title = this.label;
1052 this.value_div.style.color = this.color;
1053 if(this.selected === false)
1054 this.value_div.className = 'netdata-legend-value not-selected';
1056 this.value_div.className = 'netdata-legend-value selected';
1059 this.enabled = true;
1063 dimensionStatus.prototype.setHandler = function() {
1064 if(this.enabled === false) return;
1068 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1069 this.name_div.onclick = this.value_div.onclick = function(e) {
1071 if(ds.isSelected()) {
1073 if(e.shiftKey === true || e.ctrlKey === true) {
1074 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1077 if(ds.parent.countSelected() === 0)
1078 ds.parent.selectAll();
1081 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1082 if(ds.parent.countSelected() === 1) {
1083 ds.parent.selectAll();
1086 ds.parent.selectNone();
1092 // this is not selected
1093 if(e.shiftKey === true || e.ctrlKey === true) {
1094 // control or shift key is pressed -> select this too
1098 // no key is pressed -> select only this
1099 ds.parent.selectNone();
1104 ds.parent.state.redrawChart();
1108 dimensionStatus.prototype.select = function() {
1109 if(this.enabled === false) return;
1111 this.name_div.className = 'netdata-legend-name selected';
1112 this.value_div.className = 'netdata-legend-value selected';
1113 this.selected = true;
1116 dimensionStatus.prototype.unselect = function() {
1117 if(this.enabled === false) return;
1119 this.name_div.className = 'netdata-legend-name not-selected';
1120 this.value_div.className = 'netdata-legend-value hidden';
1121 this.selected = false;
1124 dimensionStatus.prototype.isSelected = function() {
1125 return(this.enabled === true && this.selected === true);
1128 // ----------------------------------------------------------------------------------------------------------------
1130 var dimensionsVisibility = function(state) {
1133 this.dimensions = {};
1134 this.selected_count = 0;
1135 this.unselected_count = 0;
1138 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1139 if(typeof this.dimensions[label] === 'undefined') {
1141 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1144 this.dimensions[label].setOptions(name_div, value_div, color);
1146 return this.dimensions[label];
1149 dimensionsVisibility.prototype.dimensionGet = function(label) {
1150 return this.dimensions[label];
1153 dimensionsVisibility.prototype.invalidateAll = function() {
1154 var keys = Object.keys(this.dimensions);
1155 var len = keys.length;
1157 this.dimensions[keys[len]].invalidate();
1160 dimensionsVisibility.prototype.selectAll = function() {
1161 var keys = Object.keys(this.dimensions);
1162 var len = keys.length;
1164 this.dimensions[keys[len]].select();
1167 dimensionsVisibility.prototype.countSelected = function() {
1169 var keys = Object.keys(this.dimensions);
1170 var len = keys.length;
1172 if(this.dimensions[keys[len]].isSelected()) selected++;
1177 dimensionsVisibility.prototype.selectNone = function() {
1178 var keys = Object.keys(this.dimensions);
1179 var len = keys.length;
1181 this.dimensions[keys[len]].unselect();
1184 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1186 this.selected_count = 0;
1187 this.unselected_count = 0;
1189 var len = array.length;
1191 var ds = this.dimensions[array[len]];
1192 if(typeof ds === 'undefined') {
1193 // console.log(array[i] + ' is not found');
1196 else if(ds.isSelected()) {
1198 this.selected_count++;
1202 this.unselected_count++;
1206 if(this.selected_count === 0 && this.unselected_count !== 0) {
1208 return this.selected2BooleanArray(array);
1215 // ----------------------------------------------------------------------------------------------------------------
1216 // global selection sync
1218 NETDATA.globalSelectionSync = {
1220 dont_sync_before: 0,
1225 if(this.state !== null)
1226 this.state.globalSelectionSyncStop();
1230 if(this.state !== null) {
1231 this.state.globalSelectionSyncDelay();
1236 // ----------------------------------------------------------------------------------------------------------------
1237 // Our state object, where all per-chart values are stored
1239 var chartState = function(element) {
1240 var self = $(element);
1241 this.element = element;
1244 // all private functions should use 'that', instead of 'this'
1247 /* error() - private
1248 * show an error instead of the chart
1250 var error = function(msg) {
1253 if(typeof netdataErrorCallback === 'function') {
1254 ret = netdataErrorCallback('chart', that.id, msg);
1258 that.element.innerHTML = that.id + ': ' + msg;
1259 that.enabled = false;
1260 that.current = that.pan;
1264 // GUID - a unique identifier for the chart
1265 this.uuid = NETDATA.guid();
1267 // string - the name of chart
1268 this.id = self.data('netdata');
1270 // string - the key for localStorage settings
1271 this.settings_id = self.data('id') || null;
1273 // the user given dimensions of the element
1274 this.width = self.data('width') || NETDATA.chartDefaults.width;
1275 this.height = self.data('height') || NETDATA.chartDefaults.height;
1276 this.height_original = this.height;
1278 if(this.settings_id !== null) {
1279 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1280 // this is the callback that will be called
1281 // if and when the user resets all localStorage variables
1282 // to their defaults
1284 resizeChartToHeight(height);
1288 // string - the netdata server URL, without any path
1289 this.host = self.data('host') || NETDATA.chartDefaults.host;
1291 // make sure the host does not end with /
1292 // all netdata API requests use absolute paths
1293 while(this.host.slice(-1) === '/')
1294 this.host = this.host.substring(0, this.host.length - 1);
1296 // string - the grouping method requested by the user
1297 this.method = self.data('method') || NETDATA.chartDefaults.method;
1299 // the time-range requested by the user
1300 this.after = self.data('after') || NETDATA.chartDefaults.after;
1301 this.before = self.data('before') || NETDATA.chartDefaults.before;
1303 // the pixels per point requested by the user
1304 this.pixels_per_point = self.data('pixels-per-point') || 1;
1305 this.points = self.data('points') || null;
1307 // the dimensions requested by the user
1308 this.dimensions = self.data('dimensions') || null;
1310 // the chart library requested by the user
1311 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1313 // how many retries we have made to load chart data from the server
1314 this.retries_on_data_failures = 0;
1316 // object - the chart library used
1317 this.library = null;
1321 this.colors_assigned = {};
1322 this.colors_available = null;
1324 // the element already created by the user
1325 this.element_message = null;
1327 // the element with the chart
1328 this.element_chart = null;
1330 // the element with the legend of the chart (if created by us)
1331 this.element_legend = null;
1332 this.element_legend_childs = {
1337 perfect_scroller: null, // the container to apply perfect scroller to
1341 this.chart_url = null; // string - the url to download chart info
1342 this.chart = null; // object - the chart as downloaded from the server
1344 this.title = self.data('title') || null; // the title of the chart
1345 this.units = self.data('units') || null; // the units of the chart dimensions
1346 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1347 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1349 this.running = false; // boolean - true when the chart is being refreshed now
1350 this.enabled = true; // boolean - is the chart enabled for refresh?
1351 this.paused = false; // boolean - is the chart paused for any reason?
1352 this.selected = false; // boolean - is the chart shown a selection?
1353 this.debug = false; // boolean - console.log() debug info about this chart
1355 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1356 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1357 this.requested_after = null; // milliseconds - the timestamp of the request after param
1358 this.requested_before = null; // milliseconds - the timestamp of the request before param
1359 this.requested_padding = null;
1360 this.view_after = 0;
1361 this.view_before = 0;
1363 this.value_decimal_detail = -1;
1364 var d = self.data('decimal-digits');
1365 if(typeof d === 'number') {
1366 this.value_decimal_detail = 1;
1368 this.value_decimal_detail *= 10;
1374 force_update_at: 0, // the timestamp to force the update at
1375 force_before_ms: null,
1376 force_after_ms: null
1381 force_update_at: 0, // the timestamp to force the update at
1382 force_before_ms: null,
1383 force_after_ms: null
1388 force_update_at: 0, // the timestamp to force the update at
1389 force_before_ms: null,
1390 force_after_ms: null
1393 // this is a pointer to one of the sub-classes below
1395 this.current = this.auto;
1397 // check the requested library is available
1398 // we don't initialize it here - it will be initialized when
1399 // this chart will be first used
1400 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1401 NETDATA.error(402, that.library_name);
1402 error('chart library "' + that.library_name + '" is not found');
1405 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1406 NETDATA.error(403, that.library_name);
1407 error('chart library "' + that.library_name + '" is not enabled');
1411 that.library = NETDATA.chartLibraries[that.library_name];
1413 // milliseconds - the time the last refresh took
1414 this.refresh_dt_ms = 0;
1416 // if we need to report the rendering speed
1417 // find the element that needs to be updated
1418 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1420 if(refresh_dt_element_name !== null) {
1421 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1424 this.refresh_dt_element = null;
1426 this.dimensions_visibility = new dimensionsVisibility(this);
1428 this._updating = false;
1430 // ============================================================================================================
1431 // PRIVATE FUNCTIONS
1433 var createDOM = function() {
1434 if(that.enabled === false) return;
1436 if(that.element_message !== null) that.element_message.innerHTML = '';
1437 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1438 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1440 that.element.innerHTML = '';
1442 that.element_message = document.createElement('div');
1443 that.element_message.className = 'netdata-message icon hidden';
1444 that.element.appendChild(that.element_message);
1446 that.element_chart = document.createElement('div');
1447 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1448 that.element.appendChild(that.element_chart);
1450 if(that.hasLegend() === true) {
1451 that.element.className = "netdata-container-with-legend";
1452 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1454 that.element_legend = document.createElement('div');
1455 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1456 that.element.appendChild(that.element_legend);
1459 that.element.className = "netdata-container";
1460 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1462 that.element_legend = null;
1464 that.element_legend_childs.series = null;
1466 if(typeof(that.width) === 'string')
1467 $(that.element).css('width', that.width);
1468 else if(typeof(that.width) === 'number')
1469 $(that.element).css('width', that.width + 'px');
1471 if(typeof(that.library.aspect_ratio) === 'undefined') {
1472 if(typeof(that.height) === 'string')
1473 that.element.style.height = that.height;
1474 else if(typeof(that.height) === 'number')
1475 that.element.style.height = that.height.toString() + 'px';
1478 var w = that.element.offsetWidth;
1479 if(w === null || w === 0) {
1480 // the div is hidden
1481 // this will resize the chart when next viewed
1482 that.tm.last_resized = 0;
1485 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1488 if(NETDATA.chartDefaults.min_width !== null)
1489 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1491 that.tm.last_dom_created = Date.now();
1497 * initialize state variables
1498 * destroy all (possibly) created state elements
1499 * create the basic DOM for a chart
1501 var init = function() {
1502 if(that.enabled === false) return;
1504 that.paused = false;
1505 that.selected = false;
1507 that.chart_created = false; // boolean - is the library.create() been called?
1508 that.updates_counter = 0; // numeric - the number of refreshes made so far
1509 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1510 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1513 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1514 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1515 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1517 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1518 last_updated: 0, // the timestamp the chart last updated with data
1519 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1521 // Used with NETDATA.globalPanAndZoom.seq
1522 last_visible_check: 0, // the time we last checked if it is visible
1523 last_resized: 0, // the time the chart was resized
1524 last_hidden: 0, // the time the chart was hidden
1525 last_unhidden: 0, // the time the chart was unhidden
1526 last_autorefreshed: 0 // the time the chart was last refreshed
1529 that.data = null; // the last data as downloaded from the netdata server
1530 that.data_url = 'invalid://'; // string - the last url used to update the chart
1531 that.data_points = 0; // number - the number of points returned from netdata
1532 that.data_after = 0; // milliseconds - the first timestamp of the data
1533 that.data_before = 0; // milliseconds - the last timestamp of the data
1534 that.data_update_every = 0; // milliseconds - the frequency to update the data
1536 that.tm.last_initialized = Date.now();
1539 that.setMode('auto');
1542 var maxMessageFontSize = function() {
1543 var screenHeight = screen.height;
1544 var el = that.element;
1546 // normally we want a font size, as tall as the element
1547 var h = el.clientHeight;
1549 // but give it some air, 20% let's say, or 5 pixels min
1550 var lost = Math.max(h * 0.2, 5);
1553 // center the text, vertically
1554 var paddingTop = (lost - 5) / 2;
1556 // but check the width too
1557 // it should fit 10 characters in it
1558 var w = el.clientWidth / 10;
1560 paddingTop += (h - w) / 2;
1564 // and don't make it too huge
1565 // 5% of the screen size is good
1566 if(h > screenHeight / 20) {
1567 paddingTop += (h - (screenHeight / 20)) / 2;
1568 h = screenHeight / 20;
1572 that.element_message.style.fontSize = h.toString() + 'px';
1573 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1576 var showMessageIcon = function(icon) {
1577 that.element_message.innerHTML = icon;
1578 maxMessageFontSize();
1579 $(that.element_message).removeClass('hidden');
1580 that.___messageHidden___ = undefined;
1583 var hideMessage = function() {
1584 if(typeof that.___messageHidden___ === 'undefined') {
1585 that.___messageHidden___ = true;
1586 $(that.element_message).addClass('hidden');
1590 var showRendering = function() {
1592 if(that.chart !== null) {
1593 if(that.chart.chart_type === 'line')
1594 icon = '<i class="fa fa-line-chart"></i>';
1596 icon = '<i class="fa fa-area-chart"></i>';
1599 icon = '<i class="fa fa-area-chart"></i>';
1601 showMessageIcon(icon + ' netdata');
1604 var showLoading = function() {
1605 if(that.chart_created === false) {
1606 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1612 var isHidden = function() {
1613 return (typeof that.___chartIsHidden___ !== 'undefined');
1616 // hide the chart, when it is not visible - called from isVisible()
1617 var hideChart = function() {
1618 // hide it, if it is not already hidden
1619 if(isHidden() === true) return;
1621 if(that.chart_created === true) {
1622 if(NETDATA.options.current.destroy_on_hide === true) {
1623 // we should destroy it
1628 that.element_chart.style.display = 'none';
1629 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1630 that.tm.last_hidden = Date.now();
1633 // This works, but I not sure there are no corner cases somewhere
1634 // so it is commented - if the user has memory issues he can
1635 // set Destroy on Hide for all charts
1636 // that.data = null;
1640 that.___chartIsHidden___ = true;
1643 // unhide the chart, when it is visible - called from isVisible()
1644 var unhideChart = function() {
1645 if(isHidden() === false) return;
1647 that.___chartIsHidden___ = undefined;
1648 that.updates_since_last_unhide = 0;
1650 if(that.chart_created === false) {
1651 // we need to re-initialize it, to show our background
1652 // logo in bootstrap tabs, until the chart loads
1656 that.tm.last_unhidden = Date.now();
1657 that.element_chart.style.display = '';
1658 if(that.element_legend !== null) that.element_legend.style.display = '';
1664 var canBeRendered = function() {
1665 return (isHidden() === false && that.isVisible(true) === true);
1668 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1669 var callChartLibraryUpdateSafely = function(data) {
1672 if(canBeRendered() === false)
1675 if(NETDATA.options.debug.chart_errors === true)
1676 status = that.library.update(that, data);
1679 status = that.library.update(that, data);
1686 if(status === false) {
1687 error('chart failed to be updated as ' + that.library_name);
1694 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1695 var callChartLibraryCreateSafely = function(data) {
1698 if(canBeRendered() === false)
1701 if(NETDATA.options.debug.chart_errors === true)
1702 status = that.library.create(that, data);
1705 status = that.library.create(that, data);
1712 if(status === false) {
1713 error('chart failed to be created as ' + that.library_name);
1717 that.chart_created = true;
1718 that.updates_since_last_creation = 0;
1722 // ----------------------------------------------------------------------------------------------------------------
1725 // resizeChart() - private
1726 // to be called just before the chart library to make sure that
1727 // a properly sized dom is available
1728 var resizeChart = function() {
1729 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1730 if(that.chart_created === false) return;
1732 if(that.needsRecreation()) {
1735 else if(typeof that.library.resize === 'function') {
1736 that.library.resize(that);
1738 if(that.element_legend_childs.perfect_scroller !== null)
1739 Ps.update(that.element_legend_childs.perfect_scroller);
1741 maxMessageFontSize();
1744 that.tm.last_resized = Date.now();
1748 // this is the actual chart resize algorithm
1750 // - resize the entire container
1751 // - update the internal states
1752 // - resize the chart as the div changes height
1753 // - update the scrollbar of the legend
1754 var resizeChartToHeight = function(h) {
1756 that.element.style.height = h;
1758 if(that.settings_id !== null)
1759 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1761 var now = Date.now();
1762 NETDATA.options.last_page_scroll = now;
1763 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1766 that.tm.last_resized = 0;
1770 this.resizeHandler = function(e) {
1773 if(typeof this.event_resize === 'undefined'
1774 || this.event_resize.chart_original_w === 'undefined'
1775 || this.event_resize.chart_original_h === 'undefined')
1776 this.event_resize = {
1777 chart_original_w: this.element.clientWidth,
1778 chart_original_h: this.element.clientHeight,
1782 if(e.type === 'touchstart') {
1783 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1784 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1787 this.event_resize.mouse_start_x = e.clientX;
1788 this.event_resize.mouse_start_y = e.clientY;
1791 this.event_resize.chart_start_w = this.element.clientWidth;
1792 this.event_resize.chart_start_h = this.element.clientHeight;
1793 this.event_resize.chart_last_w = this.element.clientWidth;
1794 this.event_resize.chart_last_h = this.element.clientHeight;
1796 var now = Date.now();
1797 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1798 // double click / double tap event
1800 // console.dir(this.element_legend_childs.content);
1801 // console.dir(this.element_legend_childs.perfect_scroller);
1803 // the optimal height of the chart
1804 // showing the entire legend
1805 var optimal = this.event_resize.chart_last_h
1806 + this.element_legend_childs.perfect_scroller.scrollHeight
1807 - this.element_legend_childs.perfect_scroller.clientHeight;
1809 // if we are not optimal, be optimal
1810 if(this.event_resize.chart_last_h !== optimal) {
1811 // 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());
1812 resizeChartToHeight(optimal.toString() + 'px');
1815 // else if the current height is not the original/saved height
1816 // reset to the original/saved height
1817 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1818 // 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());
1819 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1822 // else if the current height is not the internal default height
1823 // reset to the internal default height
1824 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1825 // 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());
1826 resizeChartToHeight(this.height_original.toString());
1829 // else if the current height is not the firstchild's clientheight
1831 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1832 var parent_rect = this.element.getBoundingClientRect();
1833 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1834 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1836 // console.log(parent_rect);
1837 // console.log(content_rect);
1838 // console.log(wanted);
1840 // 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' );
1841 if(this.event_resize.chart_last_h !== wanted)
1842 resizeChartToHeight(wanted.toString() + 'px');
1846 this.event_resize.last = now;
1848 // process movement event
1849 document.onmousemove =
1850 document.ontouchmove =
1851 this.element_legend_childs.resize_handler.onmousemove =
1852 this.element_legend_childs.resize_handler.ontouchmove =
1857 case 'mousemove': y = e.clientY; break;
1858 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1862 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1864 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1865 resizeChartToHeight(newH.toString() + 'px');
1866 that.event_resize.chart_last_h = newH;
1871 // process end event
1872 document.onmouseup =
1873 document.ontouchend =
1874 this.element_legend_childs.resize_handler.onmouseup =
1875 this.element_legend_childs.resize_handler.ontouchend =
1879 // remove all the hooks
1880 document.onmouseup =
1881 document.onmousemove =
1882 document.ontouchmove =
1883 document.ontouchend =
1884 that.element_legend_childs.resize_handler.onmousemove =
1885 that.element_legend_childs.resize_handler.ontouchmove =
1886 that.element_legend_childs.resize_handler.onmouseout =
1887 that.element_legend_childs.resize_handler.onmouseup =
1888 that.element_legend_childs.resize_handler.ontouchend =
1891 // allow auto-refreshes
1892 NETDATA.options.auto_refresher_stop_until = 0;
1898 var noDataToShow = function() {
1899 showMessageIcon('<i class="fa fa-warning"></i> empty');
1900 that.legendUpdateDOM();
1901 that.tm.last_autorefreshed = Date.now();
1902 // that.data_update_every = 30 * 1000;
1903 //that.element_chart.style.display = 'none';
1904 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1905 //that.___chartIsHidden___ = true;
1908 // ============================================================================================================
1911 this.error = function(msg) {
1915 this.setMode = function(m) {
1916 if(this.current !== null && this.current.name === m) return;
1919 this.current = this.auto;
1920 else if(m === 'pan')
1921 this.current = this.pan;
1922 else if(m === 'zoom')
1923 this.current = this.zoom;
1925 this.current = this.auto;
1927 this.current.force_update_at = 0;
1928 this.current.force_before_ms = null;
1929 this.current.force_after_ms = null;
1931 this.tm.last_mode_switch = Date.now();
1934 // ----------------------------------------------------------------------------------------------------------------
1935 // global selection sync
1937 // prevent to global selection sync for some time
1938 this.globalSelectionSyncDelay = function(ms) {
1939 if(NETDATA.options.current.sync_selection === false)
1942 if(typeof ms === 'number')
1943 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1945 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1948 // can we globally apply selection sync?
1949 this.globalSelectionSyncAbility = function() {
1950 if(NETDATA.options.current.sync_selection === false)
1953 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1956 this.globalSelectionSyncIsMaster = function() {
1957 return (NETDATA.globalSelectionSync.state === this);
1960 // this chart is the master of the global selection sync
1961 this.globalSelectionSyncBeMaster = function() {
1963 if(this.globalSelectionSyncIsMaster()) {
1964 if(this.debug === true)
1965 this.log('sync: I am the master already.');
1970 if(NETDATA.globalSelectionSync.state) {
1971 if(this.debug === true)
1972 this.log('sync: I am not the sync master. Resetting global sync.');
1974 this.globalSelectionSyncStop();
1977 // become the master
1978 if(this.debug === true)
1979 this.log('sync: becoming sync master.');
1981 this.selected = true;
1982 NETDATA.globalSelectionSync.state = this;
1984 // find the all slaves
1985 var targets = NETDATA.options.targets;
1986 var len = targets.length;
1988 var st = targets[len];
1991 if(this.debug === true)
1992 st.log('sync: not adding me to sync');
1994 else if(st.globalSelectionSyncIsEligible()) {
1995 if(this.debug === true)
1996 st.log('sync: adding to sync as slave');
1998 st.globalSelectionSyncBeSlave();
2002 // this.globalSelectionSyncDelay(100);
2005 // can the chart participate to the global selection sync as a slave?
2006 this.globalSelectionSyncIsEligible = function() {
2007 return (this.enabled === true
2008 && this.library !== null
2009 && typeof this.library.setSelection === 'function'
2010 && this.isVisible() === true
2011 && this.chart_created === true);
2014 // this chart becomes a slave of the global selection sync
2015 this.globalSelectionSyncBeSlave = function() {
2016 if(NETDATA.globalSelectionSync.state !== this)
2017 NETDATA.globalSelectionSync.slaves.push(this);
2020 // sync all the visible charts to the given time
2021 // this is to be called from the chart libraries
2022 this.globalSelectionSync = function(t) {
2023 if(this.globalSelectionSyncAbility() === false)
2026 if(this.globalSelectionSyncIsMaster() === false) {
2027 if(this.debug === true)
2028 this.log('sync: trying to be sync master.');
2030 this.globalSelectionSyncBeMaster();
2032 if(this.globalSelectionSyncAbility() === false)
2036 NETDATA.globalSelectionSync.last_t = t;
2037 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2042 // stop syncing all charts to the given time
2043 this.globalSelectionSyncStop = function() {
2044 if(NETDATA.globalSelectionSync.slaves.length) {
2045 if(this.debug === true)
2046 this.log('sync: cleaning up...');
2048 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2050 if(that.debug === true)
2051 st.log('sync: not adding me to sync stop');
2054 if(that.debug === true)
2055 st.log('sync: removed slave from sync');
2057 st.clearSelection();
2061 NETDATA.globalSelectionSync.last_t = 0;
2062 NETDATA.globalSelectionSync.slaves = [];
2063 NETDATA.globalSelectionSync.state = null;
2066 this.clearSelection();
2069 this.setSelection = function(t) {
2070 if(typeof this.library.setSelection === 'function')
2071 this.selected = (this.library.setSelection(this, t) === true);
2073 this.selected = true;
2075 if(this.selected === true && this.debug === true)
2076 this.log('selection set to ' + t.toString());
2078 return this.selected;
2081 this.clearSelection = function() {
2082 if(this.selected === true) {
2083 if(typeof this.library.clearSelection === 'function')
2084 this.selected = (this.library.clearSelection(this) !== true);
2086 this.selected = false;
2088 if(this.selected === false && this.debug === true)
2089 this.log('selection cleared');
2094 return this.selected;
2097 // find if a timestamp (ms) is shown in the current chart
2098 this.timeIsVisible = function(t) {
2099 return (t >= this.data_after && t <= this.data_before);
2102 this.calculateRowForTime = function(t) {
2103 if(this.timeIsVisible(t) === false) return -1;
2104 return Math.floor((t - this.data_after) / this.data_update_every);
2107 // ----------------------------------------------------------------------------------------------------------------
2110 this.log = function(msg) {
2111 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2114 this.pauseChart = function() {
2115 if(this.paused === false) {
2116 if(this.debug === true)
2117 this.log('pauseChart()');
2123 this.unpauseChart = function() {
2124 if(this.paused === true) {
2125 if(this.debug === true)
2126 this.log('unpauseChart()');
2128 this.paused = false;
2132 this.resetChart = function(dont_clear_master, dont_update) {
2133 if(this.debug === true)
2134 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2136 if(typeof dont_clear_master === 'undefined')
2137 dont_clear_master = false;
2139 if(typeof dont_update === 'undefined')
2140 dont_update = false;
2142 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2143 if(this.debug === true)
2144 this.log('resetChart() diverting to clearMaster().');
2145 // this will call us back with master === true
2146 NETDATA.globalPanAndZoom.clearMaster();
2150 this.clearSelection();
2152 this.tm.pan_and_zoom_seq = 0;
2154 this.setMode('auto');
2155 this.current.force_update_at = 0;
2156 this.current.force_before_ms = null;
2157 this.current.force_after_ms = null;
2158 this.tm.last_autorefreshed = 0;
2159 this.paused = false;
2160 this.selected = false;
2161 this.enabled = true;
2162 // this.debug = false;
2164 // do not update the chart here
2165 // or the chart will flip-flop when it is the master
2166 // of a selection sync and another chart becomes
2169 if(dont_update !== true && this.isVisible() === true) {
2174 this.updateChartPanOrZoom = function(after, before) {
2175 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2178 if(this.debug === true)
2181 if(before < after) {
2182 if(this.debug === true)
2183 this.log(logme + 'flipped parameters, rejecting it.');
2188 if(typeof this.fixed_min_duration === 'undefined')
2189 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2191 var min_duration = this.fixed_min_duration;
2192 var current_duration = Math.round(this.view_before - this.view_after);
2194 // round the numbers
2195 after = Math.round(after);
2196 before = Math.round(before);
2198 // align them to update_every
2199 // stretching them further away
2200 after -= after % this.data_update_every;
2201 before += this.data_update_every - (before % this.data_update_every);
2203 // the final wanted duration
2204 var wanted_duration = before - after;
2206 // to allow panning, accept just a point below our minimum
2207 if((current_duration - this.data_update_every) < min_duration)
2208 min_duration = current_duration - this.data_update_every;
2210 // we do it, but we adjust to minimum size and return false
2211 // when the wanted size is below the current and the minimum
2213 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2214 if(this.debug === true)
2215 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2217 min_duration = this.fixed_min_duration;
2219 var dt = (min_duration - wanted_duration) / 2;
2222 wanted_duration = before - after;
2226 var tolerance = this.data_update_every * 2;
2227 var movement = Math.abs(before - this.view_before);
2229 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2230 if(this.debug === true)
2231 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);
2235 if(this.current.name === 'auto') {
2236 this.log(logme + 'caller called me with mode: ' + this.current.name);
2237 this.setMode('pan');
2240 if(this.debug === true)
2241 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);
2243 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2244 this.current.force_after_ms = after;
2245 this.current.force_before_ms = before;
2246 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2250 this.legendFormatValue = function(value) {
2251 if(value === null || value === 'undefined') return '-';
2252 if(typeof value !== 'number') return value;
2254 if(this.value_decimal_detail !== -1)
2255 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2257 var abs = Math.abs(value);
2258 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2259 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2260 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2261 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2262 return (Math.round(value * 10000) / 10000).toLocaleString();
2265 this.legendSetLabelValue = function(label, value) {
2266 var series = this.element_legend_childs.series[label];
2267 if(typeof series === 'undefined') return;
2268 if(series.value === null && series.user === null) return;
2271 // this slows down firefox and edge significantly
2272 // since it requires to use innerHTML(), instead of innerText()
2274 // if the value has not changed, skip DOM update
2275 //if(series.last === value) return;
2278 if(typeof value === 'number') {
2279 var v = Math.abs(value);
2280 s = r = this.legendFormatValue(value);
2282 if(typeof series.last === 'number') {
2283 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2284 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2285 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2287 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2297 series.last = value;
2301 var s = this.legendFormatValue(value);
2303 // caching: do not update the update to show the same value again
2304 if(s === series.last_shown_value) return;
2305 series.last_shown_value = s;
2307 if(series.value !== null) series.value.innerText = s;
2308 if(series.user !== null) series.user.innerText = s;
2311 this.__legendSetDateString = function(date) {
2312 if(date !== this.__last_shown_legend_date) {
2313 this.element_legend_childs.title_date.innerText = date;
2314 this.__last_shown_legend_date = date;
2318 this.__legendSetTimeString = function(time) {
2319 if(time !== this.__last_shown_legend_time) {
2320 this.element_legend_childs.title_time.innerText = time;
2321 this.__last_shown_legend_time = time;
2325 this.__legendSetUnitsString = function(units) {
2326 if(units !== this.__last_shown_legend_units) {
2327 this.element_legend_childs.title_units.innerText = units;
2328 this.__last_shown_legend_units = units;
2332 this.legendSetDate = function(ms) {
2333 if(typeof ms !== 'number') {
2334 this.legendShowUndefined();
2338 var d = new Date(ms);
2340 if(this.element_legend_childs.title_date)
2341 this.__legendSetDateString(d.toLocaleDateString());
2343 if(this.element_legend_childs.title_time)
2344 this.__legendSetTimeString(d.toLocaleTimeString());
2346 if(this.element_legend_childs.title_units)
2347 this.__legendSetUnitsString(this.units)
2350 this.legendShowUndefined = function() {
2351 if(this.element_legend_childs.title_date)
2352 this.__legendSetDateString(' ');
2354 if(this.element_legend_childs.title_time)
2355 this.__legendSetTimeString(this.chart.name);
2357 if(this.element_legend_childs.title_units)
2358 this.__legendSetUnitsString(' ');
2360 if(this.data && this.element_legend_childs.series !== null) {
2361 var labels = this.data.dimension_names;
2362 var i = labels.length;
2364 var label = labels[i];
2366 if(typeof label === 'undefined') continue;
2367 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2368 this.legendSetLabelValue(label, null);
2373 this.legendShowLatestValues = function() {
2374 if(this.chart === null) return;
2375 if(this.selected) return;
2377 if(this.data === null || this.element_legend_childs.series === null) {
2378 this.legendShowUndefined();
2382 var show_undefined = true;
2383 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2384 show_undefined = false;
2386 if(show_undefined) {
2387 this.legendShowUndefined();
2391 this.legendSetDate(this.view_before);
2393 var labels = this.data.dimension_names;
2394 var i = labels.length;
2396 var label = labels[i];
2398 if(typeof label === 'undefined') continue;
2399 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2402 this.legendSetLabelValue(label, null);
2404 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2408 this.legendReset = function() {
2409 this.legendShowLatestValues();
2412 // this should be called just ONCE per dimension per chart
2413 this._chartDimensionColor = function(label) {
2414 if(this.colors === null) this.chartColors();
2416 if(typeof this.colors_assigned[label] === 'undefined') {
2417 if(this.colors_available.length === 0) {
2418 var len = NETDATA.themes.current.colors.length;
2420 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2423 this.colors_assigned[label] = this.colors_available.shift();
2425 if(this.debug === true)
2426 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2429 if(this.debug === true)
2430 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2433 this.colors.push(this.colors_assigned[label]);
2434 return this.colors_assigned[label];
2437 this.chartColors = function() {
2438 if(this.colors !== null) return this.colors;
2441 this.colors_available = [];
2443 // add the standard colors
2444 var len = NETDATA.themes.current.colors.length;
2446 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2448 // add the user supplied colors
2449 var c = $(this.element).data('colors');
2450 // this.log('read colors: ' + c);
2451 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2452 if(typeof c !== 'string') {
2453 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2463 this.colors_available.unshift(c[len]);
2464 // this.log('adding color: ' + c[len]);
2473 this.legendUpdateDOM = function() {
2474 var needed = false, dim, keys, len, i;
2476 // check that the legend DOM is up to date for the downloaded dimensions
2477 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2478 // this.log('the legend does not have any series - requesting legend update');
2481 else if(this.data === null) {
2482 // this.log('the chart does not have any data - requesting legend update');
2485 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2489 var labels = this.data.dimension_names.toString();
2490 if(labels !== this.element_legend_childs.series.labels_key) {
2493 if(this.debug === true)
2494 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2498 if(needed === false) {
2499 // make sure colors available
2502 // do we have to update the current values?
2503 // we do this, only when the visible chart is current
2504 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2505 if(this.debug === true)
2506 this.log('chart is in latest position... updating values on legend...');
2508 //var labels = this.data.dimension_names;
2509 //var i = labels.length;
2511 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2515 if(this.colors === null) {
2516 // this is the first time we update the chart
2517 // let's assign colors to all dimensions
2518 if(this.library.track_colors() === true) {
2519 keys = Object.keys(this.chart.dimensions);
2521 for(i = 0; i < len ;i++)
2522 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2525 // we will re-generate the colors for the chart
2526 // based on the selected dimensions
2529 if(this.debug === true)
2530 this.log('updating Legend DOM');
2532 // mark all dimensions as invalid
2533 this.dimensions_visibility.invalidateAll();
2535 var genLabel = function(state, parent, dim, name, count) {
2536 var color = state._chartDimensionColor(name);
2538 var user_element = null;
2539 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2540 if(user_id === null)
2541 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2542 if(user_id !== null) {
2543 user_element = document.getElementById(user_id) || null;
2544 if (user_element === null)
2545 state.log('Cannot find element with id: ' + user_id);
2548 state.element_legend_childs.series[name] = {
2549 name: document.createElement('span'),
2550 value: document.createElement('span'),
2553 last_shown_value: null
2556 var label = state.element_legend_childs.series[name];
2558 // create the dimension visibility tracking for this label
2559 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2561 var rgb = NETDATA.colorHex2Rgb(color);
2562 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2563 + state.chart.chart_type
2564 + '" style="background-color: '
2565 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2566 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2568 var text = document.createTextNode(' ' + name);
2569 label.name.appendChild(text);
2572 parent.appendChild(document.createElement('br'));
2574 parent.appendChild(label.name);
2575 parent.appendChild(label.value);
2578 var content = document.createElement('div');
2580 if(this.hasLegend()) {
2581 this.element_legend_childs = {
2583 resize_handler: document.createElement('div'),
2584 toolbox: document.createElement('div'),
2585 toolbox_left: document.createElement('div'),
2586 toolbox_right: document.createElement('div'),
2587 toolbox_reset: document.createElement('div'),
2588 toolbox_zoomin: document.createElement('div'),
2589 toolbox_zoomout: document.createElement('div'),
2590 toolbox_volume: document.createElement('div'),
2591 title_date: document.createElement('span'),
2592 title_time: document.createElement('span'),
2593 title_units: document.createElement('span'),
2594 perfect_scroller: document.createElement('div'),
2598 this.element_legend.innerHTML = '';
2600 if(this.library.toolboxPanAndZoom !== null) {
2602 var get_pan_and_zoom_step = function(event) {
2604 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2606 else if (event.shiftKey)
2607 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2609 else if (event.altKey)
2610 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2613 return NETDATA.options.current.pan_and_zoom_factor;
2616 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2617 this.element.appendChild(this.element_legend_childs.toolbox);
2619 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2620 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2621 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2622 this.element_legend_childs.toolbox_left.onclick = function(e) {
2625 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2626 var before = that.view_before - step;
2627 var after = that.view_after - step;
2628 if(after >= that.netdata_first)
2629 that.library.toolboxPanAndZoom(that, after, before);
2631 if(NETDATA.options.current.show_help === true)
2632 $(this.element_legend_childs.toolbox_left).popover({
2637 placement: 'bottom',
2638 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2640 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>'
2644 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2645 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2646 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2647 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2649 NETDATA.resetAllCharts(that);
2651 if(NETDATA.options.current.show_help === true)
2652 $(this.element_legend_childs.toolbox_reset).popover({
2657 placement: 'bottom',
2658 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2659 title: 'Chart Reset',
2660 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>'
2663 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2664 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2665 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2666 this.element_legend_childs.toolbox_right.onclick = function(e) {
2668 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2669 var before = that.view_before + step;
2670 var after = that.view_after + step;
2671 if(before <= that.netdata_last)
2672 that.library.toolboxPanAndZoom(that, after, before);
2674 if(NETDATA.options.current.show_help === true)
2675 $(this.element_legend_childs.toolbox_right).popover({
2680 placement: 'bottom',
2681 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2683 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>'
2687 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2688 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2689 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2690 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2692 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2693 var before = that.view_before - dt;
2694 var after = that.view_after + dt;
2695 that.library.toolboxPanAndZoom(that, after, before);
2697 if(NETDATA.options.current.show_help === true)
2698 $(this.element_legend_childs.toolbox_zoomin).popover({
2703 placement: 'bottom',
2704 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2705 title: 'Chart Zoom In',
2706 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>'
2709 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2710 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2711 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2712 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2714 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);
2715 var before = that.view_before + dt;
2716 var after = that.view_after - dt;
2718 that.library.toolboxPanAndZoom(that, after, before);
2720 if(NETDATA.options.current.show_help === true)
2721 $(this.element_legend_childs.toolbox_zoomout).popover({
2726 placement: 'bottom',
2727 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2728 title: 'Chart Zoom Out',
2729 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>'
2732 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2733 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2734 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2735 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2736 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2737 //e.preventDefault();
2738 //alert('clicked toolbox_volume on ' + that.id);
2742 this.element_legend_childs.toolbox = null;
2743 this.element_legend_childs.toolbox_left = null;
2744 this.element_legend_childs.toolbox_reset = null;
2745 this.element_legend_childs.toolbox_right = null;
2746 this.element_legend_childs.toolbox_zoomin = null;
2747 this.element_legend_childs.toolbox_zoomout = null;
2748 this.element_legend_childs.toolbox_volume = null;
2751 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2752 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2753 this.element.appendChild(this.element_legend_childs.resize_handler);
2754 if(NETDATA.options.current.show_help === true)
2755 $(this.element_legend_childs.resize_handler).popover({
2760 placement: 'bottom',
2761 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2762 title: 'Chart Resize',
2763 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>'
2767 this.element_legend_childs.resize_handler.onmousedown =
2769 that.resizeHandler(e);
2773 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2774 that.resizeHandler(e);
2777 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2778 this.element_legend.appendChild(this.element_legend_childs.title_date);
2780 this.element_legend.appendChild(document.createElement('br'));
2782 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2783 this.element_legend.appendChild(this.element_legend_childs.title_time);
2785 this.element_legend.appendChild(document.createElement('br'));
2787 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2788 this.element_legend.appendChild(this.element_legend_childs.title_units);
2790 this.element_legend.appendChild(document.createElement('br'));
2792 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2793 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2795 content.className = 'netdata-legend-series-content';
2796 this.element_legend_childs.perfect_scroller.appendChild(content);
2798 if(NETDATA.options.current.show_help === true)
2799 $(content).popover({
2804 placement: 'bottom',
2805 title: 'Chart Legend',
2806 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2807 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>'
2811 this.element_legend_childs = {
2813 resize_handler: null,
2816 toolbox_right: null,
2817 toolbox_reset: null,
2818 toolbox_zoomin: null,
2819 toolbox_zoomout: null,
2820 toolbox_volume: null,
2824 perfect_scroller: null,
2830 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2831 if(this.debug === true)
2832 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2834 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2835 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2840 keys = Object.keys(this.chart.dimensions);
2841 for(i = 0, len = keys.length; i < len ;i++) {
2843 tmp.push(this.chart.dimensions[dim].name);
2844 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2846 this.element_legend_childs.series.labels_key = tmp.toString();
2847 if(this.debug === true)
2848 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2851 // create a hidden div to be used for hidding
2852 // the original legend of the chart library
2853 var el = document.createElement('div');
2854 if(this.element_legend !== null)
2855 this.element_legend.appendChild(el);
2856 el.style.display = 'none';
2858 this.element_legend_childs.hidden = document.createElement('div');
2859 el.appendChild(this.element_legend_childs.hidden);
2861 if(this.element_legend_childs.perfect_scroller !== null) {
2862 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2864 wheelPropagation: true,
2865 swipePropagation: true,
2866 minScrollbarLength: null,
2867 maxScrollbarLength: null,
2868 useBothWheelAxes: false,
2869 suppressScrollX: true,
2870 suppressScrollY: false,
2871 scrollXMarginOffset: 0,
2872 scrollYMarginOffset: 0,
2875 Ps.update(this.element_legend_childs.perfect_scroller);
2878 this.legendShowLatestValues();
2881 this.hasLegend = function() {
2882 if(typeof this.___hasLegendCache___ !== 'undefined')
2883 return this.___hasLegendCache___;
2886 if(this.library && this.library.legend(this) === 'right-side') {
2887 var legend = $(this.element).data('legend') || 'yes';
2888 if(legend === 'yes') leg = true;
2891 this.___hasLegendCache___ = leg;
2895 this.legendWidth = function() {
2896 return (this.hasLegend())?140:0;
2899 this.legendHeight = function() {
2900 return $(this.element).height();
2903 this.chartWidth = function() {
2904 return $(this.element).width() - this.legendWidth();
2907 this.chartHeight = function() {
2908 return $(this.element).height();
2911 this.chartPixelsPerPoint = function() {
2912 // force an options provided detail
2913 var px = this.pixels_per_point;
2915 if(this.library && px < this.library.pixels_per_point(this))
2916 px = this.library.pixels_per_point(this);
2918 if(px < NETDATA.options.current.pixels_per_point)
2919 px = NETDATA.options.current.pixels_per_point;
2924 this.needsRecreation = function() {
2926 this.chart_created === true
2928 && this.library.autoresize() === false
2929 && this.tm.last_resized < NETDATA.options.last_resized
2933 this.chartURL = function() {
2934 var after, before, points_multiplier = 1;
2935 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2936 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2938 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2939 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2940 this.view_after = after * 1000;
2941 this.view_before = before * 1000;
2943 this.requested_padding = null;
2944 points_multiplier = 1;
2946 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2947 this.tm.pan_and_zoom_seq = 0;
2949 before = Math.round(this.current.force_before_ms / 1000);
2950 after = Math.round(this.current.force_after_ms / 1000);
2951 this.view_after = after * 1000;
2952 this.view_before = before * 1000;
2954 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2955 this.requested_padding = Math.round((before - after) / 2);
2956 after -= this.requested_padding;
2957 before += this.requested_padding;
2958 this.requested_padding *= 1000;
2959 points_multiplier = 2;
2962 this.current.force_before_ms = null;
2963 this.current.force_after_ms = null;
2966 this.tm.pan_and_zoom_seq = 0;
2968 before = this.before;
2970 this.view_after = after * 1000;
2971 this.view_before = before * 1000;
2973 this.requested_padding = null;
2974 points_multiplier = 1;
2977 this.requested_after = after * 1000;
2978 this.requested_before = before * 1000;
2980 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2982 // build the data URL
2983 this.data_url = this.host + this.chart.data_url;
2984 this.data_url += "&format=" + this.library.format();
2985 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2986 this.data_url += "&group=" + this.method;
2988 if(this.override_options !== null)
2989 this.data_url += "&options=" + this.override_options.toString();
2991 this.data_url += "&options=" + this.library.options(this);
2993 this.data_url += '|jsonwrap';
2995 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2996 this.data_url += '|nonzero';
2998 if(this.append_options !== null)
2999 this.data_url += '|' + this.append_options.toString();
3002 this.data_url += "&after=" + after.toString();
3005 this.data_url += "&before=" + before.toString();
3008 this.data_url += "&dimensions=" + this.dimensions;
3010 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3011 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3014 this.redrawChart = function() {
3015 if(this.data !== null)
3016 this.updateChartWithData(this.data);
3019 this.updateChartWithData = function(data) {
3020 if(this.debug === true)
3021 this.log('updateChartWithData() called.');
3023 // this may force the chart to be re-created
3027 this.updates_counter++;
3028 this.updates_since_last_unhide++;
3029 this.updates_since_last_creation++;
3031 var started = Date.now();
3033 // if the result is JSON, find the latest update-every
3034 this.data_update_every = data.view_update_every * 1000;
3035 this.data_after = data.after * 1000;
3036 this.data_before = data.before * 1000;
3037 this.netdata_first = data.first_entry * 1000;
3038 this.netdata_last = data.last_entry * 1000;
3039 this.data_points = data.points;
3042 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3043 if(this.view_after < this.data_after) {
3044 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3045 this.view_after = this.data_after;
3048 if(this.view_before > this.data_before) {
3049 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3050 this.view_before = this.data_before;
3054 this.view_after = this.data_after;
3055 this.view_before = this.data_before;
3058 if(this.debug === true) {
3059 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3061 if(this.current.force_after_ms)
3062 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3064 this.log('STATUS: forced : unset');
3066 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3067 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3068 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3069 this.log('STATUS: points : ' + (this.data_points).toString());
3072 if(this.data_points === 0) {
3077 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3078 if(this.debug === true)
3079 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3081 this.chart_created = false;
3084 // check and update the legend
3085 this.legendUpdateDOM();
3087 if(this.chart_created === true
3088 && typeof this.library.update === 'function') {
3090 if(this.debug === true)
3091 this.log('updating chart...');
3093 if(callChartLibraryUpdateSafely(data) === false)
3097 if(this.debug === true)
3098 this.log('creating chart...');
3100 if(callChartLibraryCreateSafely(data) === false)
3104 this.legendShowLatestValues();
3105 if(this.selected === true)
3106 NETDATA.globalSelectionSync.stop();
3108 // update the performance counters
3109 var now = Date.now();
3110 this.tm.last_updated = now;
3112 // don't update last_autorefreshed if this chart is
3113 // forced to be updated with global PanAndZoom
3114 if(NETDATA.globalPanAndZoom.isActive())
3115 this.tm.last_autorefreshed = 0;
3117 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3118 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3120 this.tm.last_autorefreshed = now;
3123 this.refresh_dt_ms = now - started;
3124 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3126 if(this.refresh_dt_element !== null)
3127 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3130 this.updateChart = function(callback) {
3131 if(this.debug === true)
3132 this.log('updateChart() called.');
3134 if(this._updating === true) {
3135 if(this.debug === true)
3136 this.log('I am already updating...');
3138 if(typeof callback === 'function')
3144 // due to late initialization of charts and libraries
3145 // we need to check this too
3146 if(this.enabled === false) {
3147 if(this.debug === true)
3148 this.log('I am not enabled');
3150 if(typeof callback === 'function')
3156 if(canBeRendered() === false) {
3157 if(typeof callback === 'function')
3163 if(this.chart === null)
3164 return this.getChart(function() {
3165 return that.updateChart(callback);
3168 if(this.library.initialized === false) {
3169 if(this.library.enabled === true) {
3170 return this.library.initialize(function () {
3171 return that.updateChart(callback);
3175 error('chart library "' + this.library_name + '" is not available.');
3177 if(typeof callback === 'function')
3184 this.clearSelection();
3187 if(this.debug === true)
3188 this.log('updating from ' + this.data_url);
3190 NETDATA.statistics.refreshes_total++;
3191 NETDATA.statistics.refreshes_active++;
3193 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3194 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3196 this._updating = true;
3198 this.xhr = $.ajax( {
3203 'Cache-Control': 'no-cache, no-store',
3204 'Pragma': 'no-cache'
3206 xhrFields: { withCredentials: true } // required for the cookie
3208 .done(function(data) {
3209 that.xhr = undefined;
3210 that.retries_on_data_failures = 0;
3212 if(that.debug === true)
3213 that.log('data received. updating chart.');
3215 that.updateChartWithData(data);
3217 .fail(function(msg) {
3218 that.xhr = undefined;
3220 if(msg.statusText !== 'abort') {
3221 that.retries_on_data_failures++;
3222 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3223 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3224 that.retries_on_data_failures = 0;
3225 error('data download failed for url: ' + that.data_url);
3228 that.tm.last_autorefreshed = Date.now();
3229 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3233 .always(function() {
3234 that.xhr = undefined;
3236 NETDATA.statistics.refreshes_active--;
3237 that._updating = false;
3239 if(typeof callback === 'function')
3244 this.isVisible = function(nocache) {
3245 if(typeof nocache === 'undefined')
3248 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3250 // caching - we do not evaluate the charts visibility
3251 // if the page has not been scrolled since the last check
3252 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3253 return this.___isVisible___;
3255 this.tm.last_visible_check = Date.now();
3257 var wh = window.innerHeight;
3258 var x = this.element.getBoundingClientRect();
3262 if(x.width === 0 || x.height === 0) {
3264 this.___isVisible___ = false;
3265 return this.___isVisible___;
3268 if(x.top < 0 && -x.top > x.height) {
3269 // the chart is entirely above
3270 ret = -x.top - x.height;
3272 else if(x.top > wh) {
3273 // the chart is entirely below
3277 if(ret > tolerance) {
3278 // the chart is too far
3281 this.___isVisible___ = false;
3282 return this.___isVisible___;
3285 // the chart is inside or very close
3288 this.___isVisible___ = true;
3289 return this.___isVisible___;
3293 this.isAutoRefreshable = function() {
3294 return (this.current.autorefresh);
3297 this.canBeAutoRefreshed = function() {
3298 var now = Date.now();
3300 if(this.running === true) {
3301 if(this.debug === true)
3302 this.log('I am already running');
3307 if(this.enabled === false) {
3308 if(this.debug === true)
3309 this.log('I am not enabled');
3314 if(this.library === null || this.library.enabled === false) {
3315 error('charting library "' + this.library_name + '" is not available');
3316 if(this.debug === true)
3317 this.log('My chart library ' + this.library_name + ' is not available');
3322 if(this.isVisible() === false) {
3323 if(NETDATA.options.debug.visibility === true || this.debug === true)
3324 this.log('I am not visible');
3329 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3330 if(this.debug === true)
3331 this.log('timed force update detected - allowing this update');
3333 this.current.force_update_at = 0;
3337 if(this.isAutoRefreshable() === true) {
3338 // allow the first update, even if the page is not visible
3339 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3340 if(NETDATA.options.debug.focus === true || this.debug === true)
3341 this.log('canBeAutoRefreshed(): page does not have focus');
3346 if(this.needsRecreation() === true) {
3347 if(this.debug === true)
3348 this.log('canBeAutoRefreshed(): needs re-creation.');
3353 // options valid only for autoRefresh()
3354 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3355 if(NETDATA.globalPanAndZoom.isActive()) {
3356 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3357 if(this.debug === true)
3358 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3363 if(this.debug === true)
3364 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3370 if(this.selected === true) {
3371 if(this.debug === true)
3372 this.log('canBeAutoRefreshed(): I have a selection in place.');
3377 if(this.paused === true) {
3378 if(this.debug === true)
3379 this.log('canBeAutoRefreshed(): I am paused.');
3384 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3385 if(this.debug === true)
3386 this.log('canBeAutoRefreshed(): It is time to update me.');
3396 this.autoRefresh = function(callback) {
3397 if(this.canBeAutoRefreshed() === true && this.running === false) {
3400 state.running = true;
3401 state.updateChart(function() {
3402 state.running = false;
3404 if(typeof callback !== 'undefined')
3409 if(typeof callback !== 'undefined')
3414 this._defaultsFromDownloadedChart = function(chart) {
3416 this.chart_url = chart.url;
3417 this.data_update_every = chart.update_every * 1000;
3418 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3419 this.tm.last_info_downloaded = Date.now();
3421 if(this.title === null)
3422 this.title = chart.title;
3424 if(this.units === null)
3425 this.units = chart.units;
3428 // fetch the chart description from the netdata server
3429 this.getChart = function(callback) {
3430 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3432 this._defaultsFromDownloadedChart(this.chart);
3434 if(typeof callback === 'function')
3438 this.chart_url = "/api/v1/chart?chart=" + this.id;
3440 if(this.debug === true)
3441 this.log('downloading ' + this.chart_url);
3444 url: this.host + this.chart_url,
3447 xhrFields: { withCredentials: true } // required for the cookie
3449 .done(function(chart) {
3450 chart.url = that.chart_url;
3451 that._defaultsFromDownloadedChart(chart);
3452 NETDATA.chartRegistry.add(that.host, that.id, chart);
3455 NETDATA.error(404, that.chart_url);
3456 error('chart not found on url "' + that.chart_url + '"');
3458 .always(function() {
3459 if(typeof callback === 'function')
3465 // ============================================================================================================
3471 NETDATA.resetAllCharts = function(state) {
3472 // first clear the global selection sync
3473 // to make sure no chart is in selected state
3474 state.globalSelectionSyncStop();
3476 // there are 2 possibilities here
3477 // a. state is the global Pan and Zoom master
3478 // b. state is not the global Pan and Zoom master
3480 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3483 // clear the global Pan and Zoom
3484 // this will also refresh the master
3485 // and unblock any charts currently mirroring the master
3486 NETDATA.globalPanAndZoom.clearMaster();
3488 // if we were not the master, reset our status too
3489 // this is required because most probably the mouse
3490 // is over this chart, blocking it from auto-refreshing
3491 if(master === false && (state.paused === true || state.selected === true))
3495 // get or create a chart state, given a DOM element
3496 NETDATA.chartState = function(element) {
3497 var state = $(element).data('netdata-state-object') || null;
3498 if(state === null) {
3499 state = new chartState(element);
3500 $(element).data('netdata-state-object', state);
3505 // ----------------------------------------------------------------------------------------------------------------
3506 // Library functions
3508 // Load a script without jquery
3509 // This is used to load jquery - after it is loaded, we use jquery
3510 NETDATA._loadjQuery = function(callback) {
3511 if(typeof jQuery === 'undefined') {
3512 if(NETDATA.options.debug.main_loop === true)
3513 console.log('loading ' + NETDATA.jQuery);
3515 var script = document.createElement('script');
3516 script.type = 'text/javascript';
3517 script.async = true;
3518 script.src = NETDATA.jQuery;
3520 // script.onabort = onError;
3521 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3522 if(typeof callback === "function")
3523 script.onload = callback;
3525 var s = document.getElementsByTagName('script')[0];
3526 s.parentNode.insertBefore(script, s);
3528 else if(typeof callback === "function")
3532 NETDATA._loadCSS = function(filename) {
3533 // don't use jQuery here
3534 // styles are loaded before jQuery
3535 // to eliminate showing an unstyled page to the user
3537 var fileref = document.createElement("link");
3538 fileref.setAttribute("rel", "stylesheet");
3539 fileref.setAttribute("type", "text/css");
3540 fileref.setAttribute("href", filename);
3542 if (typeof fileref !== 'undefined')
3543 document.getElementsByTagName("head")[0].appendChild(fileref);
3546 NETDATA.colorHex2Rgb = function(hex) {
3547 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3548 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3549 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3550 return r + r + g + g + b + b;
3553 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3555 r: parseInt(result[1], 16),
3556 g: parseInt(result[2], 16),
3557 b: parseInt(result[3], 16)
3561 NETDATA.colorLuminance = function(hex, lum) {
3562 // validate hex string
3563 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3565 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3569 // convert to decimal and change luminosity
3570 var rgb = "#", c, i;
3571 for (i = 0; i < 3; i++) {
3572 c = parseInt(hex.substr(i*2,2), 16);
3573 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3574 rgb += ("00"+c).substr(c.length);
3580 NETDATA.guid = function() {
3582 return Math.floor((1 + Math.random()) * 0x10000)
3587 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3590 NETDATA.zeropad = function(x) {
3591 if(x > -10 && x < 10) return '0' + x.toString();
3592 else return x.toString();
3595 // user function to signal us the DOM has been
3597 NETDATA.updatedDom = function() {
3598 NETDATA.options.updated_dom = true;
3601 NETDATA.ready = function(callback) {
3602 NETDATA.options.pauseCallback = callback;
3605 NETDATA.pause = function(callback) {
3606 if(typeof callback === 'function') {
3607 if (NETDATA.options.pause === true)
3610 NETDATA.options.pauseCallback = callback;
3614 NETDATA.unpause = function() {
3615 NETDATA.options.pauseCallback = null;
3616 NETDATA.options.updated_dom = true;
3617 NETDATA.options.pause = false;
3620 // ----------------------------------------------------------------------------------------------------------------
3622 // this is purely sequential charts refresher
3623 // it is meant to be autonomous
3624 NETDATA.chartRefresherNoParallel = function(index) {
3625 if(NETDATA.options.debug.main_loop === true)
3626 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3628 if(NETDATA.options.updated_dom === true) {
3629 // the dom has been updated
3630 // get the dom parts again
3631 NETDATA.parseDom(NETDATA.chartRefresher);
3634 if(index >= NETDATA.options.targets.length) {
3635 if(NETDATA.options.debug.main_loop === true)
3636 console.log('waiting to restart main loop...');
3638 NETDATA.options.auto_refresher_fast_weight = 0;
3640 setTimeout(function() {
3641 NETDATA.chartRefresher();
3642 }, NETDATA.options.current.idle_between_loops);
3645 var state = NETDATA.options.targets[index];
3647 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3648 if(NETDATA.options.debug.main_loop === true)
3649 console.log('fast rendering...');
3651 state.autoRefresh(function() {
3652 NETDATA.chartRefresherNoParallel(++index);
3656 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3657 NETDATA.options.auto_refresher_fast_weight = 0;
3659 setTimeout(function() {
3660 state.autoRefresh(function() {
3661 NETDATA.chartRefresherNoParallel(++index);
3663 }, NETDATA.options.current.idle_between_charts);
3668 NETDATA.chartRefresherWaitTime = function() {
3669 return NETDATA.options.current.idle_parallel_loops;
3672 // the default refresher
3673 NETDATA.chartRefresher = function() {
3674 // console.log('auto-refresher...');
3676 if(NETDATA.options.pause === true) {
3677 // console.log('auto-refresher is paused');
3678 setTimeout(NETDATA.chartRefresher,
3679 NETDATA.chartRefresherWaitTime());
3683 if(typeof NETDATA.options.pauseCallback === 'function') {
3684 // console.log('auto-refresher is calling pauseCallback');
3685 NETDATA.options.pause = true;
3686 NETDATA.options.pauseCallback();
3687 NETDATA.chartRefresher();
3691 if(NETDATA.options.current.parallel_refresher === false) {
3692 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3693 NETDATA.chartRefresherNoParallel(0);
3697 if(NETDATA.options.updated_dom === true) {
3698 // the dom has been updated
3699 // get the dom parts again
3700 // console.log('auto-refresher is calling parseDom()');
3701 NETDATA.parseDom(NETDATA.chartRefresher);
3706 var targets = NETDATA.options.targets;
3707 var len = targets.length;
3710 state = targets[len];
3711 if(state.isVisible() === false || state.running === true)
3714 if(state.library.initialized === false) {
3715 if(state.library.enabled === true) {
3716 state.library.initialize(NETDATA.chartRefresher);
3720 state.error('chart library "' + state.library_name + '" is not enabled.');
3724 parallel.unshift(state);
3727 if(parallel.length > 0) {
3728 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3729 // this will execute the jobs in parallel
3730 $(parallel).each(function() {
3735 // console.log('auto-refresher nothing to do');
3738 // run the next refresh iteration
3739 setTimeout(NETDATA.chartRefresher,
3740 NETDATA.chartRefresherWaitTime());
3743 NETDATA.parseDom = function(callback) {
3744 NETDATA.options.last_page_scroll = Date.now();
3745 NETDATA.options.updated_dom = false;
3747 var targets = $('div[data-netdata]'); //.filter(':visible');
3749 if(NETDATA.options.debug.main_loop === true)
3750 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3752 NETDATA.options.targets = [];
3753 var len = targets.length;
3755 // the initialization will take care of sizing
3756 // and the "loading..." message
3757 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3760 if(typeof callback === 'function')
3764 // this is the main function - where everything starts
3765 NETDATA.start = function() {
3766 // this should be called only once
3768 NETDATA.options.page_is_visible = true;
3770 $(window).blur(function() {
3771 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3772 NETDATA.options.page_is_visible = false;
3773 if(NETDATA.options.debug.focus === true)
3774 console.log('Lost Focus!');
3778 $(window).focus(function() {
3779 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3780 NETDATA.options.page_is_visible = true;
3781 if(NETDATA.options.debug.focus === true)
3782 console.log('Focus restored!');
3786 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3787 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3788 NETDATA.options.page_is_visible = false;
3789 if(NETDATA.options.debug.focus === true)
3790 console.log('Document has no focus!');
3794 // bootstrap tab switching
3795 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3797 // bootstrap modal switching
3798 var $modal = $('.modal');
3799 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3800 $modal.on('shown.bs.modal', NETDATA.onscroll);
3802 // bootstrap collapse switching
3803 var $collapse = $('.collapse');
3804 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3805 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3807 NETDATA.parseDom(NETDATA.chartRefresher);
3809 // Alarms initialization
3810 setTimeout(NETDATA.alarms.init, 1000);
3812 // Registry initialization
3813 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3815 if(typeof netdataCallback === 'function')
3819 // ----------------------------------------------------------------------------------------------------------------
3822 NETDATA.peityInitialize = function(callback) {
3823 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3825 url: NETDATA.peity_js,
3828 xhrFields: { withCredentials: true } // required for the cookie
3831 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3834 NETDATA.chartLibraries.peity.enabled = false;
3835 NETDATA.error(100, NETDATA.peity_js);
3837 .always(function() {
3838 if(typeof callback === "function")
3843 NETDATA.chartLibraries.peity.enabled = false;
3844 if(typeof callback === "function")
3849 NETDATA.peityChartUpdate = function(state, data) {
3850 state.peity_instance.innerHTML = data.result;
3852 if(state.peity_options.stroke !== state.chartColors()[0]) {
3853 state.peity_options.stroke = state.chartColors()[0];
3854 if(state.chart.chart_type === 'line')
3855 state.peity_options.fill = NETDATA.themes.current.background;
3857 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3860 $(state.peity_instance).peity('line', state.peity_options);
3864 NETDATA.peityChartCreate = function(state, data) {
3865 state.peity_instance = document.createElement('div');
3866 state.element_chart.appendChild(state.peity_instance);
3868 var self = $(state.element);
3869 state.peity_options = {
3870 stroke: NETDATA.themes.current.foreground,
3871 strokeWidth: self.data('peity-strokewidth') || 1,
3872 width: state.chartWidth(),
3873 height: state.chartHeight(),
3874 fill: NETDATA.themes.current.foreground
3877 NETDATA.peityChartUpdate(state, data);
3881 // ----------------------------------------------------------------------------------------------------------------
3884 NETDATA.sparklineInitialize = function(callback) {
3885 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3887 url: NETDATA.sparkline_js,
3890 xhrFields: { withCredentials: true } // required for the cookie
3893 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3896 NETDATA.chartLibraries.sparkline.enabled = false;
3897 NETDATA.error(100, NETDATA.sparkline_js);
3899 .always(function() {
3900 if(typeof callback === "function")
3905 NETDATA.chartLibraries.sparkline.enabled = false;
3906 if(typeof callback === "function")
3911 NETDATA.sparklineChartUpdate = function(state, data) {
3912 state.sparkline_options.width = state.chartWidth();
3913 state.sparkline_options.height = state.chartHeight();
3915 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3919 NETDATA.sparklineChartCreate = function(state, data) {
3920 var self = $(state.element);
3921 var type = self.data('sparkline-type') || 'line';
3922 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3923 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3924 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3925 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3926 var composite = self.data('sparkline-composite') || undefined;
3927 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3928 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3929 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3930 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3931 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3932 var spotColor = self.data('sparkline-spotcolor') || undefined;
3933 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3934 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3935 var spotRadius = self.data('sparkline-spotradius') || undefined;
3936 var valueSpots = self.data('sparkline-valuespots') || undefined;
3937 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3938 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3939 var lineWidth = self.data('sparkline-linewidth') || undefined;
3940 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3941 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3942 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3943 var xvalues = self.data('sparkline-xvalues') || undefined;
3944 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3945 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3946 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3947 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3948 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3949 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3950 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3951 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3952 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3953 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3954 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3955 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3956 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3957 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3958 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3959 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3960 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3961 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3962 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3963 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3964 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3965 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3967 if(spotColor === 'disable') spotColor='';
3968 if(minSpotColor === 'disable') minSpotColor='';
3969 if(maxSpotColor === 'disable') maxSpotColor='';
3971 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3973 state.sparkline_options = {
3975 lineColor: lineColor,
3976 fillColor: fillColor,
3977 chartRangeMin: chartRangeMin,
3978 chartRangeMax: chartRangeMax,
3979 composite: composite,
3980 enableTagOptions: enableTagOptions,
3981 tagOptionPrefix: tagOptionPrefix,
3982 tagValuesAttribute: tagValuesAttribute,
3983 disableHiddenCheck: disableHiddenCheck,
3984 defaultPixelsPerValue: defaultPixelsPerValue,
3985 spotColor: spotColor,
3986 minSpotColor: minSpotColor,
3987 maxSpotColor: maxSpotColor,
3988 spotRadius: spotRadius,
3989 valueSpots: valueSpots,
3990 highlightSpotColor: highlightSpotColor,
3991 highlightLineColor: highlightLineColor,
3992 lineWidth: lineWidth,
3993 normalRangeMin: normalRangeMin,
3994 normalRangeMax: normalRangeMax,
3995 drawNormalOnTop: drawNormalOnTop,
3997 chartRangeClip: chartRangeClip,
3998 chartRangeMinX: chartRangeMinX,
3999 chartRangeMaxX: chartRangeMaxX,
4000 disableInteraction: disableInteraction,
4001 disableTooltips: disableTooltips,
4002 disableHighlight: disableHighlight,
4003 highlightLighten: highlightLighten,
4004 highlightColor: highlightColor,
4005 tooltipContainer: tooltipContainer,
4006 tooltipClassname: tooltipClassname,
4007 tooltipChartTitle: state.title,
4008 tooltipFormat: tooltipFormat,
4009 tooltipPrefix: tooltipPrefix,
4010 tooltipSuffix: tooltipSuffix,
4011 tooltipSkipNull: tooltipSkipNull,
4012 tooltipValueLookups: tooltipValueLookups,
4013 tooltipFormatFieldlist: tooltipFormatFieldlist,
4014 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4015 numberFormatter: numberFormatter,
4016 numberDigitGroupSep: numberDigitGroupSep,
4017 numberDecimalMark: numberDecimalMark,
4018 numberDigitGroupCount: numberDigitGroupCount,
4019 animatedZooms: animatedZooms,
4020 width: state.chartWidth(),
4021 height: state.chartHeight()
4024 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4028 // ----------------------------------------------------------------------------------------------------------------
4035 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4036 if(after < state.netdata_first)
4037 after = state.netdata_first;
4039 if(before > state.netdata_last)
4040 before = state.netdata_last;
4042 state.setMode('zoom');
4043 state.globalSelectionSyncStop();
4044 state.globalSelectionSyncDelay();
4045 state.dygraph_user_action = true;
4046 state.dygraph_force_zoom = true;
4047 state.updateChartPanOrZoom(after, before);
4048 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4051 NETDATA.dygraphSetSelection = function(state, t) {
4052 if(typeof state.dygraph_instance !== 'undefined') {
4053 var r = state.calculateRowForTime(t);
4055 state.dygraph_instance.setSelection(r);
4057 state.dygraph_instance.clearSelection();
4058 state.legendShowUndefined();
4065 NETDATA.dygraphClearSelection = function(state) {
4066 if(typeof state.dygraph_instance !== 'undefined') {
4067 state.dygraph_instance.clearSelection();
4072 NETDATA.dygraphSmoothInitialize = function(callback) {
4074 url: NETDATA.dygraph_smooth_js,
4077 xhrFields: { withCredentials: true } // required for the cookie
4080 NETDATA.dygraph.smooth = true;
4081 smoothPlotter.smoothing = 0.3;
4084 NETDATA.dygraph.smooth = false;
4086 .always(function() {
4087 if(typeof callback === "function")
4092 NETDATA.dygraphInitialize = function(callback) {
4093 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4095 url: NETDATA.dygraph_js,
4098 xhrFields: { withCredentials: true } // required for the cookie
4101 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4104 NETDATA.chartLibraries.dygraph.enabled = false;
4105 NETDATA.error(100, NETDATA.dygraph_js);
4107 .always(function() {
4108 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4109 NETDATA.dygraphSmoothInitialize(callback);
4110 else if(typeof callback === "function")
4115 NETDATA.chartLibraries.dygraph.enabled = false;
4116 if(typeof callback === "function")
4121 NETDATA.dygraphChartUpdate = function(state, data) {
4122 var dygraph = state.dygraph_instance;
4124 if(typeof dygraph === 'undefined')
4125 return NETDATA.dygraphChartCreate(state, data);
4127 // when the chart is not visible, and hidden
4128 // if there is a window resize, dygraph detects
4129 // its element size as 0x0.
4130 // this will make it re-appear properly
4132 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4136 file: data.result.data,
4137 colors: state.chartColors(),
4138 labels: data.result.labels,
4139 labelsDivWidth: state.chartWidth() - 70,
4140 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4143 if(state.dygraph_force_zoom === true) {
4144 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4145 state.log('dygraphChartUpdate() forced zoom update');
4147 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4148 options.isZoomedIgnoreProgrammaticZoom = true;
4149 state.dygraph_force_zoom = false;
4151 else if(state.current.name !== 'auto') {
4152 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4153 state.log('dygraphChartUpdate() loose update');
4156 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4157 state.log('dygraphChartUpdate() strict update');
4159 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4160 options.isZoomedIgnoreProgrammaticZoom = true;
4163 options.valueRange = state.dygraph_options.valueRange;
4165 var oldMax = null, oldMin = null;
4166 if(state.__commonMin !== null) {
4167 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4168 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4170 if(state.__commonMax !== null) {
4171 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4172 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4175 if(state.dygraph_smooth_eligible === true) {
4176 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4177 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4178 NETDATA.dygraphChartCreate(state, data);
4183 dygraph.updateOptions(options);
4186 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4187 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4188 options.valueRange[0] = NETDATA.commonMin.get(state);
4191 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4192 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4193 options.valueRange[1] = NETDATA.commonMax.get(state);
4197 if(redraw === true) {
4198 // state.log('forcing redraw to adapt to common- min/max');
4199 dygraph.updateOptions(options);
4202 state.dygraph_last_rendered = Date.now();
4206 NETDATA.dygraphChartCreate = function(state, data) {
4207 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4208 state.log('dygraphChartCreate()');
4210 var self = $(state.element);
4212 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4213 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4215 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4217 var smooth = (NETDATA.dygraph.smooth === true)
4218 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4221 state.dygraph_options = {
4222 colors: self.data('dygraph-colors') || state.chartColors(),
4224 // leave a few pixels empty on the right of the chart
4225 rightGap: self.data('dygraph-rightgap')
4228 showRangeSelector: self.data('dygraph-showrangeselector')
4231 showRoller: self.data('dygraph-showroller')
4234 title: self.data('dygraph-title')
4237 titleHeight: self.data('dygraph-titleheight')
4240 legend: self.data('dygraph-legend')
4241 || 'always', // we need this to get selection events
4243 labels: data.result.labels,
4245 labelsDiv: self.data('dygraph-labelsdiv')
4246 || state.element_legend_childs.hidden,
4248 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4249 || { 'fontSize':'1px' },
4251 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4252 || state.chartWidth() - 70,
4254 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4257 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4263 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4266 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4269 includeZero: self.data('dygraph-includezero')
4270 || (chart_type === 'stacked'),
4272 xRangePad: self.data('dygraph-xrangepad')
4275 yRangePad: self.data('dygraph-yrangepad')
4278 valueRange: self.data('dygraph-valuerange')
4281 ylabel: state.units,
4283 yLabelWidth: self.data('dygraph-ylabelwidth')
4286 // the function to plot the chart
4289 // The width of the lines connecting data points.
4290 // This can be used to increase the contrast or some graphs.
4291 strokeWidth: self.data('dygraph-strokewidth')
4292 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4294 strokePattern: self.data('dygraph-strokepattern')
4297 // The size of the dot to draw on each point in pixels (see drawPoints).
4298 // A dot is always drawn when a point is "isolated",
4299 // i.e. there is a missing point on either side of it.
4300 // This also controls the size of those dots.
4301 drawPoints: self.data('dygraph-drawpoints')
4304 // Draw points at the edges of gaps in the data.
4305 // This improves visibility of small data segments or other data irregularities.
4306 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4309 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4312 pointSize: self.data('dygraph-pointsize')
4315 // enabling this makes the chart with little square lines
4316 stepPlot: self.data('dygraph-stepplot')
4319 // Draw a border around graph lines to make crossing lines more easily
4320 // distinguishable. Useful for graphs with many lines.
4321 strokeBorderColor: self.data('dygraph-strokebordercolor')
4322 || NETDATA.themes.current.background,
4324 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4325 || (chart_type === 'stacked')?0.0:0.0,
4327 fillGraph: self.data('dygraph-fillgraph')
4328 || (chart_type === 'area' || chart_type === 'stacked'),
4330 fillAlpha: self.data('dygraph-fillalpha')
4331 || ((chart_type === 'stacked')
4332 ?NETDATA.options.current.color_fill_opacity_stacked
4333 :NETDATA.options.current.color_fill_opacity_area),
4335 stackedGraph: self.data('dygraph-stackedgraph')
4336 || (chart_type === 'stacked'),
4338 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4341 drawAxis: self.data('dygraph-drawaxis')
4344 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4347 axisLineColor: self.data('dygraph-axislinecolor')
4348 || NETDATA.themes.current.axis,
4350 axisLineWidth: self.data('dygraph-axislinewidth')
4353 drawGrid: self.data('dygraph-drawgrid')
4356 gridLinePattern: self.data('dygraph-gridlinepattern')
4359 gridLineWidth: self.data('dygraph-gridlinewidth')
4362 gridLineColor: self.data('dygraph-gridlinecolor')
4363 || NETDATA.themes.current.grid,
4365 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4368 sigFigs: self.data('dygraph-sigfigs')
4371 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4374 valueFormatter: self.data('dygraph-valueformatter')
4375 || function(x){ return x.toFixed(2); },
4377 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4378 || highlightCircleSize,
4380 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4381 || null, // TOO SLOW: { strokeWidth: 1.5 },
4383 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4384 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4386 pointClickCallback: self.data('dygraph-pointclickcallback')
4389 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4393 ticker: Dygraph.dateTicker,
4394 axisLabelFormatter: function (d, gran) {
4396 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4398 valueFormatter: function (ms) {
4400 //var d = new Date(ms);
4401 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4403 // no need to return anything here
4410 valueFormatter: function (x) {
4411 // we format legends with the state object
4412 // no need to do anything here
4413 // return (Math.round(x*100) / 100).toLocaleString();
4414 // return state.legendFormatValue(x);
4419 legendFormatter: function(data) {
4420 var elements = state.element_legend_childs;
4422 // if the hidden div is not there
4423 // we are not managing the legend
4424 if(elements.hidden === null) return;
4426 if (typeof data.x !== 'undefined') {
4427 state.legendSetDate(data.x);
4428 var i = data.series.length;
4430 var series = data.series[i];
4431 if(series.isVisible === true)
4432 state.legendSetLabelValue(series.label, series.y);
4434 state.legendSetLabelValue(series.label, null);
4440 drawCallback: function(dygraph, is_initial) {
4441 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4442 state.dygraph_user_action = false;
4444 var x_range = dygraph.xAxisRange();
4445 var after = Math.round(x_range[0]);
4446 var before = Math.round(x_range[1]);
4448 if(NETDATA.options.debug.dygraph === true)
4449 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4451 if(before <= state.netdata_last && after >= state.netdata_first)
4452 state.updateChartPanOrZoom(after, before);
4455 zoomCallback: function(minDate, maxDate, yRanges) {
4458 if(NETDATA.options.debug.dygraph === true)
4459 state.log('dygraphZoomCallback()');
4461 state.globalSelectionSyncStop();
4462 state.globalSelectionSyncDelay();
4463 state.setMode('zoom');
4465 // refresh it to the greatest possible zoom level
4466 state.dygraph_user_action = true;
4467 state.dygraph_force_zoom = true;
4468 state.updateChartPanOrZoom(minDate, maxDate);
4470 highlightCallback: function(event, x, points, row, seriesName) {
4473 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4474 state.log('dygraphHighlightCallback()');
4478 // there is a bug in dygraph when the chart is zoomed enough
4479 // the time it thinks is selected is wrong
4480 // here we calculate the time t based on the row number selected
4482 // var t = state.data_after + row * state.data_update_every;
4483 // 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);
4485 state.globalSelectionSync(x);
4487 // fix legend zIndex using the internal structures of dygraph legend module
4488 // this works, but it is a hack!
4489 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4491 unhighlightCallback: function(event) {
4494 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4495 state.log('dygraphUnhighlightCallback()');
4497 state.unpauseChart();
4498 state.globalSelectionSyncStop();
4500 interactionModel : {
4501 mousedown: function(event, dygraph, context) {
4502 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4503 state.log('interactionModel.mousedown()');
4505 state.dygraph_user_action = true;
4506 state.globalSelectionSyncStop();
4508 if(NETDATA.options.debug.dygraph === true)
4509 state.log('dygraphMouseDown()');
4511 // Right-click should not initiate a zoom.
4512 if(event.button && event.button === 2) return;
4514 context.initializeMouseDown(event, dygraph, context);
4516 if(event.button && event.button === 1) {
4517 if (event.altKey || event.shiftKey) {
4518 state.setMode('pan');
4519 state.globalSelectionSyncDelay();
4520 Dygraph.startPan(event, dygraph, context);
4523 state.setMode('zoom');
4524 state.globalSelectionSyncDelay();
4525 Dygraph.startZoom(event, dygraph, context);
4529 if (event.altKey || event.shiftKey) {
4530 state.setMode('zoom');
4531 state.globalSelectionSyncDelay();
4532 Dygraph.startZoom(event, dygraph, context);
4535 state.setMode('pan');
4536 state.globalSelectionSyncDelay();
4537 Dygraph.startPan(event, dygraph, context);
4541 mousemove: function(event, dygraph, context) {
4542 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4543 state.log('interactionModel.mousemove()');
4545 if(context.isPanning) {
4546 state.dygraph_user_action = true;
4547 state.globalSelectionSyncStop();
4548 state.globalSelectionSyncDelay();
4549 state.setMode('pan');
4550 context.is2DPan = false;
4551 Dygraph.movePan(event, dygraph, context);
4553 else if(context.isZooming) {
4554 state.dygraph_user_action = true;
4555 state.globalSelectionSyncStop();
4556 state.globalSelectionSyncDelay();
4557 state.setMode('zoom');
4558 Dygraph.moveZoom(event, dygraph, context);
4561 mouseup: function(event, dygraph, context) {
4562 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4563 state.log('interactionModel.mouseup()');
4565 if (context.isPanning) {
4566 state.dygraph_user_action = true;
4567 state.globalSelectionSyncDelay();
4568 Dygraph.endPan(event, dygraph, context);
4570 else if (context.isZooming) {
4571 state.dygraph_user_action = true;
4572 state.globalSelectionSyncDelay();
4573 Dygraph.endZoom(event, dygraph, context);
4576 click: function(event, dygraph, context) {
4580 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4581 state.log('interactionModel.click()');
4583 event.preventDefault();
4585 dblclick: function(event, dygraph, context) {
4590 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4591 state.log('interactionModel.dblclick()');
4592 NETDATA.resetAllCharts(state);
4594 wheel: function(event, dygraph, context) {
4597 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4598 state.log('interactionModel.wheel()');
4600 // Take the offset of a mouse event on the dygraph canvas and
4601 // convert it to a pair of percentages from the bottom left.
4602 // (Not top left, bottom is where the lower value is.)
4603 function offsetToPercentage(g, offsetX, offsetY) {
4604 // This is calculating the pixel offset of the leftmost date.
4605 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4606 var yar0 = g.yAxisRange(0);
4608 // This is calculating the pixel of the highest value. (Top pixel)
4609 var yOffset = g.toDomCoords(null, yar0[1])[1];
4611 // x y w and h are relative to the corner of the drawing area,
4612 // so that the upper corner of the drawing area is (0, 0).
4613 var x = offsetX - xOffset;
4614 var y = offsetY - yOffset;
4616 // This is computing the rightmost pixel, effectively defining the
4618 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4620 // This is computing the lowest pixel, effectively defining the height.
4621 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4623 // Percentage from the left.
4624 var xPct = w === 0 ? 0 : (x / w);
4625 // Percentage from the top.
4626 var yPct = h === 0 ? 0 : (y / h);
4628 // The (1-) part below changes it from "% distance down from the top"
4629 // to "% distance up from the bottom".
4630 return [xPct, (1-yPct)];
4633 // Adjusts [x, y] toward each other by zoomInPercentage%
4634 // Split it so the left/bottom axis gets xBias/yBias of that change and
4635 // tight/top gets (1-xBias)/(1-yBias) of that change.
4637 // If a bias is missing it splits it down the middle.
4638 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4639 xBias = xBias || 0.5;
4640 yBias = yBias || 0.5;
4642 function adjustAxis(axis, zoomInPercentage, bias) {
4643 var delta = axis[1] - axis[0];
4644 var increment = delta * zoomInPercentage;
4645 var foo = [increment * bias, increment * (1-bias)];
4647 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4650 var yAxes = g.yAxisRanges();
4652 for (var i = 0; i < yAxes.length; i++) {
4653 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4656 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4659 if(event.altKey || event.shiftKey) {
4660 state.dygraph_user_action = true;
4662 state.globalSelectionSyncStop();
4663 state.globalSelectionSyncDelay();
4665 // http://dygraphs.com/gallery/interaction-api.js
4667 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4669 normal_def = event.wheelDelta / 40;
4672 normal_def = event.deltaY * -1.2;
4674 var normal = (event.detail) ? event.detail * -1 : normal_def;
4675 var percentage = normal / 50;
4677 if (!(event.offsetX && event.offsetY)){
4678 event.offsetX = event.layerX - event.target.offsetLeft;
4679 event.offsetY = event.layerY - event.target.offsetTop;
4682 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4683 var xPct = percentages[0];
4684 var yPct = percentages[1];
4686 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4687 var after = new_x_range[0];
4688 var before = new_x_range[1];
4690 var first = state.netdata_first + state.data_update_every;
4691 var last = state.netdata_last + state.data_update_every;
4694 after -= (before - last);
4701 state.setMode('zoom');
4702 if(state.updateChartPanOrZoom(after, before) === true)
4703 dygraph.updateOptions({ dateWindow: [ after, before ] });
4705 event.preventDefault();
4708 touchstart: function(event, dygraph, context) {
4709 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4710 state.log('interactionModel.touchstart()');
4712 state.dygraph_user_action = true;
4713 state.setMode('zoom');
4716 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4718 // we overwrite the touch directions at the end, to overwrite
4719 // the internal default of dygraph
4720 context.touchDirections = { x: true, y: false };
4722 state.dygraph_last_touch_start = Date.now();
4723 state.dygraph_last_touch_move = 0;
4725 if(typeof event.touches[0].pageX === 'number')
4726 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4728 state.dygraph_last_touch_page_x = 0;
4730 touchmove: function(event, dygraph, context) {
4731 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4732 state.log('interactionModel.touchmove()');
4734 state.dygraph_user_action = true;
4735 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4737 state.dygraph_last_touch_move = Date.now();
4739 touchend: function(event, dygraph, context) {
4740 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4741 state.log('interactionModel.touchend()');
4743 state.dygraph_user_action = true;
4744 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4746 // if it didn't move, it is a selection
4747 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4748 // internal api of dygraph
4749 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4750 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4751 if(NETDATA.dygraphSetSelection(state, t) === true)
4752 state.globalSelectionSync(t);
4755 // if it was double tap within double click time, reset the charts
4756 var now = Date.now();
4757 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4758 if(state.dygraph_last_touch_move === 0) {
4759 var dt = now - state.dygraph_last_touch_end;
4760 if(dt <= NETDATA.options.current.double_click_speed)
4761 NETDATA.resetAllCharts(state);
4765 // remember the timestamp of the last touch end
4766 state.dygraph_last_touch_end = now;
4771 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4772 state.dygraph_options.drawGrid = false;
4773 state.dygraph_options.drawAxis = false;
4774 state.dygraph_options.title = undefined;
4775 state.dygraph_options.ylabel = undefined;
4776 state.dygraph_options.yLabelWidth = 0;
4777 state.dygraph_options.labelsDivWidth = 120;
4778 state.dygraph_options.labelsDivStyles.width = '120px';
4779 state.dygraph_options.labelsSeparateLines = true;
4780 state.dygraph_options.rightGap = 0;
4781 state.dygraph_options.yRangePad = 1;
4784 if(smooth === true) {
4785 state.dygraph_smooth_eligible = true;
4787 if(NETDATA.options.current.smooth_plot === true)
4788 state.dygraph_options.plotter = smoothPlotter;
4790 else state.dygraph_smooth_eligible = false;
4792 state.dygraph_instance = new Dygraph(state.element_chart,
4793 data.result.data, state.dygraph_options);
4795 state.dygraph_force_zoom = false;
4796 state.dygraph_user_action = false;
4797 state.dygraph_last_rendered = Date.now();
4799 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4800 state.__commonMin = self.data('common-min') || null;
4801 state.__commonMax = self.data('common-max') || null;
4804 state.log('incompatible version of Dygraph detected');
4805 state.__commonMin = null;
4806 state.__commonMax = null;
4812 // ----------------------------------------------------------------------------------------------------------------
4815 NETDATA.morrisInitialize = function(callback) {
4816 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4818 // morris requires raphael
4819 if(!NETDATA.chartLibraries.raphael.initialized) {
4820 if(NETDATA.chartLibraries.raphael.enabled) {
4821 NETDATA.raphaelInitialize(function() {
4822 NETDATA.morrisInitialize(callback);
4826 NETDATA.chartLibraries.morris.enabled = false;
4827 if(typeof callback === "function")
4832 NETDATA._loadCSS(NETDATA.morris_css);
4835 url: NETDATA.morris_js,
4838 xhrFields: { withCredentials: true } // required for the cookie
4841 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4844 NETDATA.chartLibraries.morris.enabled = false;
4845 NETDATA.error(100, NETDATA.morris_js);
4847 .always(function() {
4848 if(typeof callback === "function")
4854 NETDATA.chartLibraries.morris.enabled = false;
4855 if(typeof callback === "function")
4860 NETDATA.morrisChartUpdate = function(state, data) {
4861 state.morris_instance.setData(data.result.data);
4865 NETDATA.morrisChartCreate = function(state, data) {
4867 state.morris_options = {
4868 element: state.element_chart.id,
4869 data: data.result.data,
4871 ykeys: data.dimension_names,
4872 labels: data.dimension_names,
4878 continuousLine: false,
4879 behaveLikeLine: false
4882 if(state.chart.chart_type === 'line')
4883 state.morris_instance = new Morris.Line(state.morris_options);
4885 else if(state.chart.chart_type === 'area') {
4886 state.morris_options.behaveLikeLine = true;
4887 state.morris_instance = new Morris.Area(state.morris_options);
4890 state.morris_instance = new Morris.Area(state.morris_options);
4895 // ----------------------------------------------------------------------------------------------------------------
4898 NETDATA.raphaelInitialize = function(callback) {
4899 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4901 url: NETDATA.raphael_js,
4904 xhrFields: { withCredentials: true } // required for the cookie
4907 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4910 NETDATA.chartLibraries.raphael.enabled = false;
4911 NETDATA.error(100, NETDATA.raphael_js);
4913 .always(function() {
4914 if(typeof callback === "function")
4919 NETDATA.chartLibraries.raphael.enabled = false;
4920 if(typeof callback === "function")
4925 NETDATA.raphaelChartUpdate = function(state, data) {
4926 $(state.element_chart).raphael(data.result, {
4927 width: state.chartWidth(),
4928 height: state.chartHeight()
4934 NETDATA.raphaelChartCreate = function(state, data) {
4935 $(state.element_chart).raphael(data.result, {
4936 width: state.chartWidth(),
4937 height: state.chartHeight()
4943 // ----------------------------------------------------------------------------------------------------------------
4946 NETDATA.c3Initialize = function(callback) {
4947 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4950 if(!NETDATA.chartLibraries.d3.initialized) {
4951 if(NETDATA.chartLibraries.d3.enabled) {
4952 NETDATA.d3Initialize(function() {
4953 NETDATA.c3Initialize(callback);
4957 NETDATA.chartLibraries.c3.enabled = false;
4958 if(typeof callback === "function")
4963 NETDATA._loadCSS(NETDATA.c3_css);
4969 xhrFields: { withCredentials: true } // required for the cookie
4972 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4975 NETDATA.chartLibraries.c3.enabled = false;
4976 NETDATA.error(100, NETDATA.c3_js);
4978 .always(function() {
4979 if(typeof callback === "function")
4985 NETDATA.chartLibraries.c3.enabled = false;
4986 if(typeof callback === "function")
4991 NETDATA.c3ChartUpdate = function(state, data) {
4992 state.c3_instance.destroy();
4993 return NETDATA.c3ChartCreate(state, data);
4995 //state.c3_instance.load({
4996 // rows: data.result,
5003 NETDATA.c3ChartCreate = function(state, data) {
5005 state.element_chart.id = 'c3-' + state.uuid;
5006 // console.log('id = ' + state.element_chart.id);
5008 state.c3_instance = c3.generate({
5009 bindto: '#' + state.element_chart.id,
5011 width: state.chartWidth(),
5012 height: state.chartHeight()
5015 pattern: state.chartColors()
5020 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5026 format: function(x) {
5027 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5054 // console.log(state.c3_instance);
5059 // ----------------------------------------------------------------------------------------------------------------
5062 NETDATA.d3Initialize = function(callback) {
5063 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5068 xhrFields: { withCredentials: true } // required for the cookie
5071 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5074 NETDATA.chartLibraries.d3.enabled = false;
5075 NETDATA.error(100, NETDATA.d3_js);
5077 .always(function() {
5078 if(typeof callback === "function")
5083 NETDATA.chartLibraries.d3.enabled = false;
5084 if(typeof callback === "function")
5089 NETDATA.d3ChartUpdate = function(state, data) {
5096 NETDATA.d3ChartCreate = function(state, data) {
5103 // ----------------------------------------------------------------------------------------------------------------
5106 NETDATA.googleInitialize = function(callback) {
5107 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5109 url: NETDATA.google_js,
5112 xhrFields: { withCredentials: true } // required for the cookie
5115 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5116 google.load('visualization', '1.1', {
5117 'packages': ['corechart', 'controls'],
5118 'callback': callback
5122 NETDATA.chartLibraries.google.enabled = false;
5123 NETDATA.error(100, NETDATA.google_js);
5124 if(typeof callback === "function")
5129 NETDATA.chartLibraries.google.enabled = false;
5130 if(typeof callback === "function")
5135 NETDATA.googleChartUpdate = function(state, data) {
5136 var datatable = new google.visualization.DataTable(data.result);
5137 state.google_instance.draw(datatable, state.google_options);
5141 NETDATA.googleChartCreate = function(state, data) {
5142 var datatable = new google.visualization.DataTable(data.result);
5144 state.google_options = {
5145 colors: state.chartColors(),
5147 // do not set width, height - the chart resizes itself
5148 //width: state.chartWidth(),
5149 //height: state.chartHeight(),
5154 // title: "Time of Day",
5155 // format:'HH:mm:ss',
5156 viewWindowMode: 'maximized',
5168 viewWindowMode: 'pretty',
5183 focusTarget: 'category',
5190 titlePosition: 'out',
5201 curveType: 'function',
5206 switch(state.chart.chart_type) {
5208 state.google_options.vAxis.viewWindowMode = 'maximized';
5209 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5210 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5214 state.google_options.isStacked = true;
5215 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5216 state.google_options.vAxis.viewWindowMode = 'maximized';
5217 state.google_options.vAxis.minValue = null;
5218 state.google_options.vAxis.maxValue = null;
5219 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5224 state.google_options.lineWidth = 2;
5225 state.google_instance = new google.visualization.LineChart(state.element_chart);
5229 state.google_instance.draw(datatable, state.google_options);
5233 // ----------------------------------------------------------------------------------------------------------------
5235 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5236 if(typeof value !== 'number') value = 0;
5237 if(typeof min !== 'number') min = 0;
5238 if(typeof max !== 'number') max = 0;
5240 if(min > value) min = value;
5241 if(max < value) max = value;
5243 // make sure it is zero based
5244 if(min > 0) min = 0;
5245 if(max < 0) max = 0;
5250 pcent = Math.round(value * 100 / max);
5251 if(pcent === 0) pcent = 0.1;
5255 pcent = Math.round(-value * 100 / min);
5256 if(pcent === 0) pcent = -0.1;
5262 // ----------------------------------------------------------------------------------------------------------------
5265 NETDATA.easypiechartInitialize = function(callback) {
5266 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5268 url: NETDATA.easypiechart_js,
5271 xhrFields: { withCredentials: true } // required for the cookie
5274 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5277 NETDATA.chartLibraries.easypiechart.enabled = false;
5278 NETDATA.error(100, NETDATA.easypiechart_js);
5280 .always(function() {
5281 if(typeof callback === "function")
5286 NETDATA.chartLibraries.easypiechart.enabled = false;
5287 if(typeof callback === "function")
5292 NETDATA.easypiechartClearSelection = function(state) {
5293 if(typeof state.easyPieChartEvent !== 'undefined') {
5294 if(state.easyPieChartEvent.timer !== undefined) {
5295 clearTimeout(state.easyPieChartEvent.timer);
5298 state.easyPieChartEvent.timer = undefined;
5301 if(state.isAutoRefreshable() === true && state.data !== null) {
5302 NETDATA.easypiechartChartUpdate(state, state.data);
5305 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5306 state.easyPieChart_instance.update(0);
5308 state.easyPieChart_instance.enableAnimation();
5313 NETDATA.easypiechartSetSelection = function(state, t) {
5314 if(state.timeIsVisible(t) !== true)
5315 return NETDATA.easypiechartClearSelection(state);
5317 var slot = state.calculateRowForTime(t);
5318 if(slot < 0 || slot >= state.data.result.length)
5319 return NETDATA.easypiechartClearSelection(state);
5321 if(typeof state.easyPieChartEvent === 'undefined') {
5322 state.easyPieChartEvent = {
5329 var value = state.data.result[state.data.result.length - 1 - slot];
5330 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5331 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5332 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5334 state.easyPieChartEvent.value = value;
5335 state.easyPieChartEvent.pcent = pcent;
5336 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5338 if(state.easyPieChartEvent.timer === undefined) {
5339 state.easyPieChart_instance.disableAnimation();
5341 state.easyPieChartEvent.timer = setTimeout(function() {
5342 state.easyPieChartEvent.timer = undefined;
5343 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5344 }, NETDATA.options.current.charts_selection_animation_delay);
5350 NETDATA.easypiechartChartUpdate = function(state, data) {
5351 var value, min, max, pcent;
5353 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5358 value = data.result[0];
5359 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5360 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5361 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5364 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5365 state.easyPieChart_instance.update(pcent);
5369 NETDATA.easypiechartChartCreate = function(state, data) {
5370 var self = $(state.element);
5371 var chart = $(state.element_chart);
5373 var value = data.result[0];
5374 var min = self.data('easypiechart-min-value') || null;
5375 var max = self.data('easypiechart-max-value') || null;
5376 var adjust = self.data('easypiechart-adjust') || null;
5379 min = NETDATA.commonMin.get(state);
5380 state.easyPieChartMin = null;
5383 state.easyPieChartMin = min;
5386 max = NETDATA.commonMax.get(state);
5387 state.easyPieChartMax = null;
5390 state.easyPieChartMax = max;
5392 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5394 chart.data('data-percent', pcent);
5398 case 'width': size = state.chartHeight(); break;
5399 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5400 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5402 default: size = state.chartWidth(); break;
5404 state.element.style.width = size + 'px';
5405 state.element.style.height = size + 'px';
5407 var stroke = Math.floor(size / 22);
5408 if(stroke < 3) stroke = 2;
5410 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5411 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5412 state.easyPieChartLabel = document.createElement('span');
5413 state.easyPieChartLabel.className = 'easyPieChartLabel';
5414 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5415 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5416 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5417 state.element_chart.appendChild(state.easyPieChartLabel);
5419 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5420 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5421 state.easyPieChartTitle = document.createElement('span');
5422 state.easyPieChartTitle.className = 'easyPieChartTitle';
5423 state.easyPieChartTitle.innerText = state.title;
5424 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5425 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5426 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5427 state.element_chart.appendChild(state.easyPieChartTitle);
5429 var unitfontsize = Math.round(titlefontsize * 0.9);
5430 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5431 state.easyPieChartUnits = document.createElement('span');
5432 state.easyPieChartUnits.className = 'easyPieChartUnits';
5433 state.easyPieChartUnits.innerText = state.units;
5434 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5435 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5436 state.element_chart.appendChild(state.easyPieChartUnits);
5438 var barColor = self.data('easypiechart-barcolor');
5439 if(typeof barColor === 'undefined' || barColor === null)
5440 barColor = state.chartColors()[0];
5442 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5443 var tmp = eval(barColor);
5444 if(typeof tmp === 'function')
5448 chart.easyPieChart({
5450 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5451 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5452 scaleLength: self.data('easypiechart-scalelength') || 5,
5453 lineCap: self.data('easypiechart-linecap') || 'round',
5454 lineWidth: self.data('easypiechart-linewidth') || stroke,
5455 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5456 size: self.data('easypiechart-size') || size,
5457 rotate: self.data('easypiechart-rotate') || 0,
5458 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5459 easing: self.data('easypiechart-easing') || undefined
5462 // when we just re-create the chart
5463 // do not animate the first update
5465 if(typeof state.easyPieChart_instance !== 'undefined')
5468 state.easyPieChart_instance = chart.data('easyPieChart');
5469 if(animate === false) state.easyPieChart_instance.disableAnimation();
5470 state.easyPieChart_instance.update(pcent);
5471 if(animate === false) state.easyPieChart_instance.enableAnimation();
5475 // ----------------------------------------------------------------------------------------------------------------
5478 NETDATA.gaugeInitialize = function(callback) {
5479 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5481 url: NETDATA.gauge_js,
5484 xhrFields: { withCredentials: true } // required for the cookie
5487 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5490 NETDATA.chartLibraries.gauge.enabled = false;
5491 NETDATA.error(100, NETDATA.gauge_js);
5493 .always(function() {
5494 if(typeof callback === "function")
5499 NETDATA.chartLibraries.gauge.enabled = false;
5500 if(typeof callback === "function")
5505 NETDATA.gaugeAnimation = function(state, status) {
5508 if(typeof status === 'boolean' && status === false)
5510 else if(typeof status === 'number')
5513 // console.log('gauge speed ' + speed);
5514 state.gauge_instance.animationSpeed = speed;
5515 state.___gaugeOld__.speed = speed;
5518 NETDATA.gaugeSet = function(state, value, min, max) {
5519 if(typeof value !== 'number') value = 0;
5520 if(typeof min !== 'number') min = 0;
5521 if(typeof max !== 'number') max = 0;
5522 if(value > max) max = value;
5523 if(value < min) min = value;
5529 else if(min === max)
5532 // gauge.js has an issue if the needle
5533 // is smaller than min or larger than max
5534 // when we set the new values
5535 // the needle will go crazy
5537 // to prevent it, we always feed it
5538 // with a percentage, so that the needle
5539 // is always between min and max
5540 var pcent = (value - min) * 100 / (max - min);
5542 // bug fix for gauge.js 1.3.1
5543 // if the value is the absolute min or max, the chart is broken
5544 if(pcent < 0.001) pcent = 0.001;
5545 if(pcent > 99.999) pcent = 99.999;
5547 state.gauge_instance.set(pcent);
5548 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5550 state.___gaugeOld__.value = value;
5551 state.___gaugeOld__.min = min;
5552 state.___gaugeOld__.max = max;
5555 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5556 if(state.___gaugeOld__.valueLabel !== value) {
5557 state.___gaugeOld__.valueLabel = value;
5558 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5560 if(state.___gaugeOld__.minLabel !== min) {
5561 state.___gaugeOld__.minLabel = min;
5562 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5564 if(state.___gaugeOld__.maxLabel !== max) {
5565 state.___gaugeOld__.maxLabel = max;
5566 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5570 NETDATA.gaugeClearSelection = function(state) {
5571 if(typeof state.gaugeEvent !== 'undefined') {
5572 if(state.gaugeEvent.timer !== undefined) {
5573 clearTimeout(state.gaugeEvent.timer);
5576 state.gaugeEvent.timer = undefined;
5579 if(state.isAutoRefreshable() === true && state.data !== null) {
5580 NETDATA.gaugeChartUpdate(state, state.data);
5583 NETDATA.gaugeAnimation(state, false);
5584 NETDATA.gaugeSet(state, null, null, null);
5585 NETDATA.gaugeSetLabels(state, null, null, null);
5588 NETDATA.gaugeAnimation(state, true);
5592 NETDATA.gaugeSetSelection = function(state, t) {
5593 if(state.timeIsVisible(t) !== true)
5594 return NETDATA.gaugeClearSelection(state);
5596 var slot = state.calculateRowForTime(t);
5597 if(slot < 0 || slot >= state.data.result.length)
5598 return NETDATA.gaugeClearSelection(state);
5600 if(typeof state.gaugeEvent === 'undefined') {
5601 state.gaugeEvent = {
5609 var value = state.data.result[state.data.result.length - 1 - slot];
5610 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5611 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5613 // make sure it is zero based
5614 if(min > 0) min = 0;
5615 if(max < 0) max = 0;
5617 state.gaugeEvent.value = value;
5618 state.gaugeEvent.min = min;
5619 state.gaugeEvent.max = max;
5620 NETDATA.gaugeSetLabels(state, value, min, max);
5622 if(state.gaugeEvent.timer === undefined) {
5623 NETDATA.gaugeAnimation(state, false);
5625 state.gaugeEvent.timer = setTimeout(function() {
5626 state.gaugeEvent.timer = undefined;
5627 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5628 }, NETDATA.options.current.charts_selection_animation_delay);
5634 NETDATA.gaugeChartUpdate = function(state, data) {
5635 var value, min, max;
5637 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5641 NETDATA.gaugeSetLabels(state, null, null, null);
5644 value = data.result[0];
5645 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5646 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5647 if(value < min) min = value;
5648 if(value > max) max = value;
5650 // make sure it is zero based
5651 if(min > 0) min = 0;
5652 if(max < 0) max = 0;
5654 NETDATA.gaugeSetLabels(state, value, min, max);
5657 NETDATA.gaugeSet(state, value, min, max);
5661 NETDATA.gaugeChartCreate = function(state, data) {
5662 var self = $(state.element);
5663 // var chart = $(state.element_chart);
5665 var value = data.result[0];
5666 var min = self.data('gauge-min-value') || null;
5667 var max = self.data('gauge-max-value') || null;
5668 var adjust = self.data('gauge-adjust') || null;
5669 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5670 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5671 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5672 var stopColor = self.data('gauge-stop-color') || void 0;
5673 var generateGradient = self.data('gauge-generate-gradient') || false;
5676 min = NETDATA.commonMin.get(state);
5677 state.gaugeMin = null;
5680 state.gaugeMin = min;
5683 max = NETDATA.commonMax.get(state);
5684 state.gaugeMax = null;
5687 state.gaugeMax = max;
5689 // make sure it is zero based
5690 if(min > 0) min = 0;
5691 if(max < 0) max = 0;
5693 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5695 // case 'width': width = height * ratio; break;
5697 // default: height = width / ratio; break;
5699 //state.element.style.width = width.toString() + 'px';
5700 //state.element.style.height = height.toString() + 'px';
5705 lines: 12, // The number of lines to draw
5706 angle: 0.15, // The span of the gauge arc
5707 lineWidth: 0.50, // The line thickness
5708 radiusScale: 0.85, // Relative radius
5710 length: 0.8, // 0.9 The radius of the inner circle
5711 strokeWidth: 0.035, // The rotation offset
5712 color: pointerColor // Fill color
5714 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5715 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5716 colorStart: startColor, // Colors
5717 colorStop: stopColor, // just experiment with them
5718 strokeColor: strokeColor, // to see which ones work best for you
5719 generateGradient: (generateGradient === true),
5721 highDpiSupport: true // High resolution support
5724 if (generateGradient.constructor === Array) {
5726 // data-gauge-generate-gradient="[0, 50, 100]"
5727 // data-gauge-gradient-percent-color-0="#FFFFFF"
5728 // data-gauge-gradient-percent-color-50="#999900"
5729 // data-gauge-gradient-percent-color-100="#000000"
5731 options.percentColors = [];
5732 var len = generateGradient.length;
5734 var pcent = generateGradient[len];
5735 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5736 if(color !== false) {
5740 options.percentColors.unshift(a);
5743 if(options.percentColors.length === 0)
5744 delete options.percentColors;
5746 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5747 //noinspection PointlessArithmeticExpressionJS
5748 options.percentColors = [
5749 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5750 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5751 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5752 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5753 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5754 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5755 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5756 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5757 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5758 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5759 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5762 state.gauge_canvas = document.createElement('canvas');
5763 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5764 state.gauge_canvas.className = 'gaugeChart';
5765 state.gauge_canvas.width = width;
5766 state.gauge_canvas.height = height;
5767 state.element_chart.appendChild(state.gauge_canvas);
5769 var valuefontsize = Math.floor(height / 6);
5770 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5771 state.gaugeChartLabel = document.createElement('span');
5772 state.gaugeChartLabel.className = 'gaugeChartLabel';
5773 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5774 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5775 state.element_chart.appendChild(state.gaugeChartLabel);
5777 var titlefontsize = Math.round(valuefontsize / 2);
5779 state.gaugeChartTitle = document.createElement('span');
5780 state.gaugeChartTitle.className = 'gaugeChartTitle';
5781 state.gaugeChartTitle.innerText = state.title;
5782 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5783 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5784 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5785 state.element_chart.appendChild(state.gaugeChartTitle);
5787 var unitfontsize = Math.round(titlefontsize * 0.9);
5788 state.gaugeChartUnits = document.createElement('span');
5789 state.gaugeChartUnits.className = 'gaugeChartUnits';
5790 state.gaugeChartUnits.innerText = state.units;
5791 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5792 state.element_chart.appendChild(state.gaugeChartUnits);
5794 state.gaugeChartMin = document.createElement('span');
5795 state.gaugeChartMin.className = 'gaugeChartMin';
5796 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5797 state.element_chart.appendChild(state.gaugeChartMin);
5799 state.gaugeChartMax = document.createElement('span');
5800 state.gaugeChartMax.className = 'gaugeChartMax';
5801 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5802 state.element_chart.appendChild(state.gaugeChartMax);
5804 // when we just re-create the chart
5805 // do not animate the first update
5807 if(typeof state.gauge_instance !== 'undefined')
5810 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5812 state.___gaugeOld__ = {
5821 // we will always feed a percentage
5822 state.gauge_instance.minValue = 0;
5823 state.gauge_instance.maxValue = 100;
5825 NETDATA.gaugeAnimation(state, animate);
5826 NETDATA.gaugeSet(state, value, min, max);
5827 NETDATA.gaugeSetLabels(state, value, min, max);
5828 NETDATA.gaugeAnimation(state, true);
5832 // ----------------------------------------------------------------------------------------------------------------
5833 // Charts Libraries Registration
5835 NETDATA.chartLibraries = {
5837 initialize: NETDATA.dygraphInitialize,
5838 create: NETDATA.dygraphChartCreate,
5839 update: NETDATA.dygraphChartUpdate,
5840 resize: function(state) {
5841 if(typeof state.dygraph_instance.resize === 'function')
5842 state.dygraph_instance.resize();
5844 setSelection: NETDATA.dygraphSetSelection,
5845 clearSelection: NETDATA.dygraphClearSelection,
5846 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5849 format: function(state) { void(state); return 'json'; },
5850 options: function(state) { void(state); return 'ms|flip'; },
5851 legend: function(state) {
5852 return (this.isSparkline(state) === false)?'right-side':null;
5854 autoresize: function(state) { void(state); return true; },
5855 max_updates_to_recreate: function(state) { void(state); return 5000; },
5856 track_colors: function(state) { void(state); return true; },
5857 pixels_per_point: function(state) {
5858 return (this.isSparkline(state) === false)?3:2;
5860 isSparkline: function(state) {
5861 if(typeof state.dygraph_sparkline === 'undefined') {
5862 var t = $(state.element).data('dygraph-theme');
5863 state.dygraph_sparkline = (t === 'sparkline');
5865 return state.dygraph_sparkline;
5869 initialize: NETDATA.sparklineInitialize,
5870 create: NETDATA.sparklineChartCreate,
5871 update: NETDATA.sparklineChartUpdate,
5873 setSelection: undefined, // function(state, t) { void(state); return true; },
5874 clearSelection: undefined, // function(state) { void(state); return true; },
5875 toolboxPanAndZoom: null,
5878 format: function(state) { void(state); return 'array'; },
5879 options: function(state) { void(state); return 'flip|abs'; },
5880 legend: function(state) { void(state); return null; },
5881 autoresize: function(state) { void(state); return false; },
5882 max_updates_to_recreate: function(state) { void(state); return 5000; },
5883 track_colors: function(state) { void(state); return false; },
5884 pixels_per_point: function(state) { void(state); return 3; }
5887 initialize: NETDATA.peityInitialize,
5888 create: NETDATA.peityChartCreate,
5889 update: NETDATA.peityChartUpdate,
5891 setSelection: undefined, // function(state, t) { void(state); return true; },
5892 clearSelection: undefined, // function(state) { void(state); return true; },
5893 toolboxPanAndZoom: null,
5896 format: function(state) { void(state); return 'ssvcomma'; },
5897 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5898 legend: function(state) { void(state); return null; },
5899 autoresize: function(state) { void(state); return false; },
5900 max_updates_to_recreate: function(state) { void(state); return 5000; },
5901 track_colors: function(state) { void(state); return false; },
5902 pixels_per_point: function(state) { void(state); return 3; }
5905 initialize: NETDATA.morrisInitialize,
5906 create: NETDATA.morrisChartCreate,
5907 update: NETDATA.morrisChartUpdate,
5909 setSelection: undefined, // function(state, t) { void(state); return true; },
5910 clearSelection: undefined, // function(state) { void(state); return true; },
5911 toolboxPanAndZoom: null,
5914 format: function(state) { void(state); return 'json'; },
5915 options: function(state) { void(state); return 'objectrows|ms'; },
5916 legend: function(state) { void(state); return null; },
5917 autoresize: function(state) { void(state); return false; },
5918 max_updates_to_recreate: function(state) { void(state); return 50; },
5919 track_colors: function(state) { void(state); return false; },
5920 pixels_per_point: function(state) { void(state); return 15; }
5923 initialize: NETDATA.googleInitialize,
5924 create: NETDATA.googleChartCreate,
5925 update: NETDATA.googleChartUpdate,
5927 setSelection: undefined, //function(state, t) { void(state); return true; },
5928 clearSelection: undefined, //function(state) { void(state); return true; },
5929 toolboxPanAndZoom: null,
5932 format: function(state) { void(state); return 'datatable'; },
5933 options: function(state) { void(state); return ''; },
5934 legend: function(state) { void(state); return null; },
5935 autoresize: function(state) { void(state); return false; },
5936 max_updates_to_recreate: function(state) { void(state); return 300; },
5937 track_colors: function(state) { void(state); return false; },
5938 pixels_per_point: function(state) { void(state); return 4; }
5941 initialize: NETDATA.raphaelInitialize,
5942 create: NETDATA.raphaelChartCreate,
5943 update: NETDATA.raphaelChartUpdate,
5945 setSelection: undefined, // function(state, t) { void(state); return true; },
5946 clearSelection: undefined, // function(state) { void(state); return true; },
5947 toolboxPanAndZoom: null,
5950 format: function(state) { void(state); return 'json'; },
5951 options: function(state) { void(state); return ''; },
5952 legend: function(state) { void(state); return null; },
5953 autoresize: function(state) { void(state); return false; },
5954 max_updates_to_recreate: function(state) { void(state); return 5000; },
5955 track_colors: function(state) { void(state); return false; },
5956 pixels_per_point: function(state) { void(state); return 3; }
5959 initialize: NETDATA.c3Initialize,
5960 create: NETDATA.c3ChartCreate,
5961 update: NETDATA.c3ChartUpdate,
5963 setSelection: undefined, // function(state, t) { void(state); return true; },
5964 clearSelection: undefined, // function(state) { void(state); return true; },
5965 toolboxPanAndZoom: null,
5968 format: function(state) { void(state); return 'csvjsonarray'; },
5969 options: function(state) { void(state); return 'milliseconds'; },
5970 legend: function(state) { void(state); return null; },
5971 autoresize: function(state) { void(state); return false; },
5972 max_updates_to_recreate: function(state) { void(state); return 5000; },
5973 track_colors: function(state) { void(state); return false; },
5974 pixels_per_point: function(state) { void(state); return 15; }
5977 initialize: NETDATA.d3Initialize,
5978 create: NETDATA.d3ChartCreate,
5979 update: NETDATA.d3ChartUpdate,
5981 setSelection: undefined, // function(state, t) { void(state); return true; },
5982 clearSelection: undefined, // function(state) { void(state); return true; },
5983 toolboxPanAndZoom: null,
5986 format: function(state) { void(state); return 'json'; },
5987 options: function(state) { void(state); return ''; },
5988 legend: function(state) { void(state); return null; },
5989 autoresize: function(state) { void(state); return false; },
5990 max_updates_to_recreate: function(state) { void(state); return 5000; },
5991 track_colors: function(state) { void(state); return false; },
5992 pixels_per_point: function(state) { void(state); return 3; }
5995 initialize: NETDATA.easypiechartInitialize,
5996 create: NETDATA.easypiechartChartCreate,
5997 update: NETDATA.easypiechartChartUpdate,
5999 setSelection: NETDATA.easypiechartSetSelection,
6000 clearSelection: NETDATA.easypiechartClearSelection,
6001 toolboxPanAndZoom: null,
6004 format: function(state) { void(state); return 'array'; },
6005 options: function(state) { void(state); return 'absolute'; },
6006 legend: function(state) { void(state); return null; },
6007 autoresize: function(state) { void(state); return false; },
6008 max_updates_to_recreate: function(state) { void(state); return 5000; },
6009 track_colors: function(state) { void(state); return true; },
6010 pixels_per_point: function(state) { void(state); return 3; },
6014 initialize: NETDATA.gaugeInitialize,
6015 create: NETDATA.gaugeChartCreate,
6016 update: NETDATA.gaugeChartUpdate,
6018 setSelection: NETDATA.gaugeSetSelection,
6019 clearSelection: NETDATA.gaugeClearSelection,
6020 toolboxPanAndZoom: null,
6023 format: function(state) { void(state); return 'array'; },
6024 options: function(state) { void(state); return 'absolute'; },
6025 legend: function(state) { void(state); return null; },
6026 autoresize: function(state) { void(state); return false; },
6027 max_updates_to_recreate: function(state) { void(state); return 5000; },
6028 track_colors: function(state) { void(state); return true; },
6029 pixels_per_point: function(state) { void(state); return 3; },
6034 NETDATA.registerChartLibrary = function(library, url) {
6035 if(NETDATA.options.debug.libraries === true)
6036 console.log("registering chart library: " + library);
6038 NETDATA.chartLibraries[library].url = url;
6039 NETDATA.chartLibraries[library].initialized = true;
6040 NETDATA.chartLibraries[library].enabled = true;
6043 // ----------------------------------------------------------------------------------------------------------------
6044 // Load required JS libraries and CSS
6046 NETDATA.requiredJs = [
6048 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6050 isAlreadyLoaded: function() {
6051 // check if bootstrap is loaded
6052 if(typeof $().emulateTransitionEnd === 'function')
6055 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6060 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6061 isAlreadyLoaded: function() { return false; }
6065 NETDATA.requiredCSS = [
6067 url: NETDATA.themes.current.bootstrap_css,
6068 isAlreadyLoaded: function() {
6069 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6073 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6074 isAlreadyLoaded: function() { return false; }
6077 url: NETDATA.themes.current.dashboard_css,
6078 isAlreadyLoaded: function() { return false; }
6082 NETDATA.loadedRequiredJs = 0;
6083 NETDATA.loadRequiredJs = function(index, callback) {
6084 if(index >= NETDATA.requiredJs.length) {
6085 if(typeof callback === 'function')
6090 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6091 NETDATA.loadedRequiredJs++;
6092 NETDATA.loadRequiredJs(++index, callback);
6096 if(NETDATA.options.debug.main_loop === true)
6097 console.log('loading ' + NETDATA.requiredJs[index].url);
6100 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6104 url: NETDATA.requiredJs[index].url,
6107 xhrFields: { withCredentials: true } // required for the cookie
6110 if(NETDATA.options.debug.main_loop === true)
6111 console.log('loaded ' + NETDATA.requiredJs[index].url);
6114 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6116 .always(function() {
6117 NETDATA.loadedRequiredJs++;
6120 NETDATA.loadRequiredJs(++index, callback);
6124 NETDATA.loadRequiredJs(++index, callback);
6127 NETDATA.loadRequiredCSS = function(index) {
6128 if(index >= NETDATA.requiredCSS.length)
6131 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6132 NETDATA.loadRequiredCSS(++index);
6136 if(NETDATA.options.debug.main_loop === true)
6137 console.log('loading ' + NETDATA.requiredCSS[index].url);
6139 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6140 NETDATA.loadRequiredCSS(++index);
6144 // ----------------------------------------------------------------------------------------------------------------
6145 // Registry of netdata hosts
6148 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6149 chart_div_offset: 100, // give that space above the chart when scrolling to it
6150 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6151 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6153 ms_penalty: 0, // the time penalty of the next alarm
6154 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6155 // if alarms are shown faster than: one per 500ms
6157 notifications: false, // when true, the browser supports notifications (may not be granted though)
6158 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6159 first_notification_id: 0, // the id of the first alarm_log entry for this session
6160 // this is used to prevent CLEAR notifications for past events
6161 // notifications_shown: [],
6163 server: null, // the server to connect to for fetching alarms
6164 current: null, // the list of raised alarms - updated in the background
6165 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6167 notify: function(entry) {
6168 // console.log('alarm ' + entry.unique_id);
6170 if(entry.updated === true) {
6171 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6175 var value_string = entry.value_string;
6177 if(NETDATA.alarms.current !== null) {
6178 // get the current value_string
6179 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6180 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6181 value_string = t.value_string;
6184 var name = entry.name.replace(/_/g, ' ');
6185 var status = entry.status.toLowerCase();
6186 var title = name + ' = ' + value_string.toString();
6187 var tag = entry.alarm_id;
6188 var icon = 'images/seo-performance-128.png';
6189 var interaction = false;
6193 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6195 switch(entry.status) {
6203 case 'UNINITIALIZED':
6207 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6208 // console.log('alarm ' + entry.unique_id + ' is not current');
6211 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6212 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6215 if(entry.no_clear_notification === true) {
6216 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6219 title = name + ' back to normal (' + value_string.toString() + ')';
6220 icon = 'images/check-mark-2-128-green.png';
6221 interaction = false;
6225 if(entry.old_status === 'CRITICAL')
6226 status = 'demoted to ' + entry.status.toLowerCase();
6228 icon = 'images/alert-128-orange.png';
6229 interaction = false;
6233 if(entry.old_status === 'WARNING')
6234 status = 'escalated to ' + entry.status.toLowerCase();
6236 icon = 'images/alert-128-red.png';
6241 console.log('invalid alarm status ' + entry.status);
6246 // cleanup old notifications with the same alarm_id as this one
6247 // FIXME: it does not seem to work on any web browser!
6248 var len = NETDATA.alarms.notifications_shown.length;
6250 var n = NETDATA.alarms.notifications_shown[len];
6251 if(n.data.alarm_id === entry.alarm_id) {
6252 console.log('removing old alarm ' + n.data.unique_id);
6254 // close the notification
6257 // remove it from the array
6258 NETDATA.alarms.notifications_shown.splice(len, 1);
6259 len = NETDATA.alarms.notifications_shown.length;
6266 setTimeout(function() {
6267 // show this notification
6268 // console.log('new notification: ' + title);
6269 var n = new Notification(title, {
6270 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6272 requireInteraction: interaction,
6273 icon: NETDATA.serverDefault + icon,
6277 n.onclick = function(event) {
6278 event.preventDefault();
6279 NETDATA.alarms.onclick(event.target.data);
6283 // NETDATA.alarms.notifications_shown.push(n);
6284 // console.log(entry);
6285 }, NETDATA.alarms.ms_penalty);
6287 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6291 scrollToChart: function(chart_id) {
6292 if(typeof chart_id === 'string') {
6293 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6294 if(typeof offset !== 'undefined') {
6295 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6302 scrollToAlarm: function(alarm) {
6303 if(typeof alarm === 'object') {
6304 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6306 if(ret === true && NETDATA.options.page_is_visible === false)
6308 // 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.');
6313 notifyAll: function() {
6314 // console.log('FETCHING ALARM LOG');
6315 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6316 // console.log('ALARM LOG FETCHED');
6318 if(data === null || typeof data !== 'object') {
6319 console.log('invalid alarms log response');
6323 if(data.length === 0) {
6324 console.log('received empty alarm log');
6328 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6330 data.sort(function(a, b) {
6331 if(a.unique_id > b.unique_id) return -1;
6332 if(a.unique_id < b.unique_id) return 1;
6336 NETDATA.alarms.ms_penalty = 0;
6338 var len = data.length;
6340 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6341 NETDATA.alarms.notify(data[len]);
6344 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6347 NETDATA.alarms.last_notification_id = data[0].unique_id;
6348 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6349 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6353 check_notifications: function() {
6354 // returns true if we should fire 1+ notifications
6356 if(NETDATA.alarms.notifications !== true) {
6357 // console.log('notifications not available');
6361 if(Notification.permission !== 'granted') {
6362 // console.log('notifications not granted');
6366 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6367 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6369 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6370 // console.log('new alarms detected');
6373 //else console.log('no new alarms');
6375 // else console.log('cannot process alarms');
6380 get: function(what, callback) {
6382 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6386 'Cache-Control': 'no-cache, no-store',
6387 'Pragma': 'no-cache'
6389 xhrFields: { withCredentials: true } // required for the cookie
6391 .done(function(data) {
6392 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6393 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6395 if(typeof callback === 'function')
6396 return callback(data);
6399 NETDATA.error(415, NETDATA.alarms.server);
6401 if(typeof callback === 'function')
6402 return callback(null);
6406 update_forever: function() {
6407 NETDATA.alarms.get('active', function(data) {
6409 NETDATA.alarms.current = data;
6411 if(NETDATA.alarms.check_notifications() === true) {
6412 NETDATA.alarms.notifyAll();
6415 if (typeof NETDATA.alarms.callback === 'function') {
6416 NETDATA.alarms.callback(data);
6419 // Health monitoring is disabled on this netdata
6420 if(data.status === false) return;
6423 setTimeout(NETDATA.alarms.update_forever, 10000);
6427 get_log: function(last_id, callback) {
6428 // console.log('fetching all log after ' + last_id.toString());
6430 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6434 'Cache-Control': 'no-cache, no-store',
6435 'Pragma': 'no-cache'
6437 xhrFields: { withCredentials: true } // required for the cookie
6439 .done(function(data) {
6440 if(typeof callback === 'function')
6441 return callback(data);
6444 NETDATA.error(416, NETDATA.alarms.server);
6446 if(typeof callback === 'function')
6447 return callback(null);
6452 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6454 NETDATA.alarms.last_notification_id =
6455 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6457 if(NETDATA.alarms.onclick === null)
6458 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6460 if(netdataShowAlarms === true) {
6461 NETDATA.alarms.update_forever();
6463 if('Notification' in window) {
6464 // console.log('notifications available');
6465 NETDATA.alarms.notifications = true;
6467 if(Notification.permission === 'default')
6468 Notification.requestPermission();
6474 // ----------------------------------------------------------------------------------------------------------------
6475 // Registry of netdata hosts
6477 NETDATA.registry = {
6478 server: null, // the netdata registry server
6479 person_guid: null, // the unique ID of this browser / user
6480 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6481 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6482 machines: null, // the user's other URLs
6483 machines_array: null, // the user's other URLs in an array
6486 parsePersonUrls: function(person_urls) {
6487 // console.log(person_urls);
6488 NETDATA.registry.person_urls = person_urls;
6491 NETDATA.registry.machines = {};
6492 NETDATA.registry.machines_array = [];
6494 var apu = person_urls;
6497 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6498 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6504 accesses: apu[i][3],
6508 obj.alternate_urls.push(apu[i][1]);
6510 NETDATA.registry.machines[apu[i][0]] = obj;
6511 NETDATA.registry.machines_array.push(obj);
6514 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6516 var pu = NETDATA.registry.machines[apu[i][0]];
6517 if(pu.last_t < apu[i][2]) {
6519 pu.last_t = apu[i][2];
6520 pu.name = apu[i][4];
6522 pu.accesses += apu[i][3];
6523 pu.alternate_urls.push(apu[i][1]);
6528 if(typeof netdataRegistryCallback === 'function')
6529 netdataRegistryCallback(NETDATA.registry.machines_array);
6533 if(netdataRegistry !== true) return;
6535 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6537 NETDATA.registry.server = data.registry;
6538 NETDATA.registry.machine_guid = data.machine_guid;
6539 NETDATA.registry.hostname = data.hostname;
6541 NETDATA.registry.access(2, function (person_urls) {
6542 NETDATA.registry.parsePersonUrls(person_urls);
6549 hello: function(host, callback) {
6550 host = NETDATA.fixHost(host);
6552 // send HELLO to a netdata server:
6553 // 1. verifies the server is reachable
6554 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6556 url: host + '/api/v1/registry?action=hello',
6560 'Cache-Control': 'no-cache, no-store',
6561 'Pragma': 'no-cache'
6563 xhrFields: { withCredentials: true } // required for the cookie
6565 .done(function(data) {
6566 if(typeof data.status !== 'string' || data.status !== 'ok') {
6567 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6571 if(typeof callback === 'function')
6572 return callback(data);
6575 NETDATA.error(407, host);
6577 if(typeof callback === 'function')
6578 return callback(null);
6582 access: function(max_redirects, callback) {
6583 // send ACCESS to a netdata registry:
6584 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6585 // 2. it responds with a list of netdata servers we know
6586 // the registry identifies us using a cookie it sets the first time we access it
6587 // the registry may respond with a redirect URL to send us to another registry
6589 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),
6593 'Cache-Control': 'no-cache, no-store',
6594 'Pragma': 'no-cache'
6596 xhrFields: { withCredentials: true } // required for the cookie
6598 .done(function(data) {
6599 var redirect = null;
6600 if(typeof data.registry === 'string')
6601 redirect = data.registry;
6603 if(typeof data.status !== 'string' || data.status !== 'ok') {
6604 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6609 if(redirect !== null && max_redirects > 0) {
6610 NETDATA.registry.server = redirect;
6611 NETDATA.registry.access(max_redirects - 1, callback);
6614 if(typeof callback === 'function')
6615 return callback(null);
6619 if(typeof data.person_guid === 'string')
6620 NETDATA.registry.person_guid = data.person_guid;
6622 if(typeof callback === 'function')
6623 return callback(data.urls);
6627 NETDATA.error(410, NETDATA.registry.server);
6629 if(typeof callback === 'function')
6630 return callback(null);
6634 delete: function(delete_url, callback) {
6635 // send DELETE to a netdata registry:
6637 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),
6641 'Cache-Control': 'no-cache, no-store',
6642 'Pragma': 'no-cache'
6644 xhrFields: { withCredentials: true } // required for the cookie
6646 .done(function(data) {
6647 if(typeof data.status !== 'string' || data.status !== 'ok') {
6648 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6652 if(typeof callback === 'function')
6653 return callback(data);
6656 NETDATA.error(412, NETDATA.registry.server);
6658 if(typeof callback === 'function')
6659 return callback(null);
6663 search: function(machine_guid, callback) {
6664 // SEARCH for the URLs of a machine:
6666 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,
6670 'Cache-Control': 'no-cache, no-store',
6671 'Pragma': 'no-cache'
6673 xhrFields: { withCredentials: true } // required for the cookie
6675 .done(function(data) {
6676 if(typeof data.status !== 'string' || data.status !== 'ok') {
6677 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6681 if(typeof callback === 'function')
6682 return callback(data);
6685 NETDATA.error(418, NETDATA.registry.server);
6687 if(typeof callback === 'function')
6688 return callback(null);
6692 switch: function(new_person_guid, callback) {
6695 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,
6699 'Cache-Control': 'no-cache, no-store',
6700 'Pragma': 'no-cache'
6702 xhrFields: { withCredentials: true } // required for the cookie
6704 .done(function(data) {
6705 if(typeof data.status !== 'string' || data.status !== 'ok') {
6706 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6710 if(typeof callback === 'function')
6711 return callback(data);
6714 NETDATA.error(414, NETDATA.registry.server);
6716 if(typeof callback === 'function')
6717 return callback(null);
6722 // ----------------------------------------------------------------------------------------------------------------
6725 if(typeof netdataPrepCallback === 'function')
6726 netdataPrepCallback();
6728 NETDATA.errorReset();
6729 NETDATA.loadRequiredCSS(0);
6731 NETDATA._loadjQuery(function() {
6732 NETDATA.loadRequiredJs(0, function() {
6733 if(typeof $().emulateTransitionEnd !== 'function') {
6734 // bootstrap is not available
6735 NETDATA.options.current.show_help = false;
6738 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6739 if(NETDATA.options.debug.main_loop === true)
6740 console.log('starting chart refresh thread');
6746 })(window, document);