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.legendSetDateLast = {
2338 this.legendSetDate = function(ms) {
2339 if(typeof ms !== 'number') {
2340 this.legendShowUndefined();
2344 if(this.legendSetDateLast.ms !== ms) {
2345 var d = new Date(ms);
2346 this.legendSetDateLast.ms = ms;
2347 this.legendSetDateLast.date = d.toLocaleDateString();
2348 this.legendSetDateLast.time = d.toLocaleTimeString();
2351 if(this.element_legend_childs.title_date !== null)
2352 this.__legendSetDateString(this.legendSetDateLast.date);
2354 if(this.element_legend_childs.title_time !== null)
2355 this.__legendSetTimeString(this.legendSetDateLast.time);
2357 if(this.element_legend_childs.title_units !== null)
2358 this.__legendSetUnitsString(this.units)
2361 this.legendShowUndefined = function() {
2362 if(this.element_legend_childs.title_date !== null)
2363 this.__legendSetDateString(' haha ');
2365 if(this.element_legend_childs.title_time !== null)
2366 this.__legendSetTimeString(this.chart.name);
2368 if(this.element_legend_childs.title_units !== null)
2369 this.__legendSetUnitsString(' haha2 ');
2371 if(this.data && this.element_legend_childs.series !== null) {
2372 var labels = this.data.dimension_names;
2373 var i = labels.length;
2375 var label = labels[i];
2377 if(typeof label === 'undefined' || typeof this.element_legend_childs.series[label] === 'undefined') continue;
2378 this.legendSetLabelValue(label, null);
2383 this.legendShowLatestValues = function() {
2384 if(this.chart === null) return;
2385 if(this.selected) return;
2387 if(this.data === null || this.element_legend_childs.series === null) {
2388 this.legendShowUndefined();
2392 var show_undefined = true;
2393 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2394 show_undefined = false;
2396 if(show_undefined) {
2397 this.legendShowUndefined();
2401 this.legendSetDate(this.view_before);
2403 var labels = this.data.dimension_names;
2404 var i = labels.length;
2406 var label = labels[i];
2408 if(typeof label === 'undefined') continue;
2409 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2412 this.legendSetLabelValue(label, null);
2414 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2418 this.legendReset = function() {
2419 this.legendShowLatestValues();
2422 // this should be called just ONCE per dimension per chart
2423 this._chartDimensionColor = function(label) {
2424 if(this.colors === null) this.chartColors();
2426 if(typeof this.colors_assigned[label] === 'undefined') {
2427 if(this.colors_available.length === 0) {
2428 var len = NETDATA.themes.current.colors.length;
2430 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2433 this.colors_assigned[label] = this.colors_available.shift();
2435 if(this.debug === true)
2436 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2439 if(this.debug === true)
2440 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2443 this.colors.push(this.colors_assigned[label]);
2444 return this.colors_assigned[label];
2447 this.chartColors = function() {
2448 if(this.colors !== null) return this.colors;
2451 this.colors_available = [];
2453 // add the standard colors
2454 var len = NETDATA.themes.current.colors.length;
2456 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2458 // add the user supplied colors
2459 var c = $(this.element).data('colors');
2460 // this.log('read colors: ' + c);
2461 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2462 if(typeof c !== 'string') {
2463 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2473 this.colors_available.unshift(c[len]);
2474 // this.log('adding color: ' + c[len]);
2483 this.legendUpdateDOM = function() {
2484 var needed = false, dim, keys, len, i;
2486 // check that the legend DOM is up to date for the downloaded dimensions
2487 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2488 // this.log('the legend does not have any series - requesting legend update');
2491 else if(this.data === null) {
2492 // this.log('the chart does not have any data - requesting legend update');
2495 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2499 var labels = this.data.dimension_names.toString();
2500 if(labels !== this.element_legend_childs.series.labels_key) {
2503 if(this.debug === true)
2504 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2508 if(needed === false) {
2509 // make sure colors available
2512 // do we have to update the current values?
2513 // we do this, only when the visible chart is current
2514 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2515 if(this.debug === true)
2516 this.log('chart is in latest position... updating values on legend...');
2518 //var labels = this.data.dimension_names;
2519 //var i = labels.length;
2521 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2525 if(this.colors === null) {
2526 // this is the first time we update the chart
2527 // let's assign colors to all dimensions
2528 if(this.library.track_colors() === true) {
2529 keys = Object.keys(this.chart.dimensions);
2531 for(i = 0; i < len ;i++)
2532 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2535 // we will re-generate the colors for the chart
2536 // based on the selected dimensions
2539 if(this.debug === true)
2540 this.log('updating Legend DOM');
2542 // mark all dimensions as invalid
2543 this.dimensions_visibility.invalidateAll();
2545 var genLabel = function(state, parent, dim, name, count) {
2546 var color = state._chartDimensionColor(name);
2548 var user_element = null;
2549 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2550 if(user_id === null)
2551 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2552 if(user_id !== null) {
2553 user_element = document.getElementById(user_id) || null;
2554 if (user_element === null)
2555 state.log('Cannot find element with id: ' + user_id);
2558 state.element_legend_childs.series[name] = {
2559 name: document.createElement('span'),
2560 value: document.createElement('span'),
2563 last_shown_value: null
2566 var label = state.element_legend_childs.series[name];
2568 // create the dimension visibility tracking for this label
2569 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2571 var rgb = NETDATA.colorHex2Rgb(color);
2572 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2573 + state.chart.chart_type
2574 + '" style="background-color: '
2575 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2576 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2578 var text = document.createTextNode(' ' + name);
2579 label.name.appendChild(text);
2582 parent.appendChild(document.createElement('br'));
2584 parent.appendChild(label.name);
2585 parent.appendChild(label.value);
2588 var content = document.createElement('div');
2590 if(this.hasLegend()) {
2591 this.element_legend_childs = {
2593 resize_handler: document.createElement('div'),
2594 toolbox: document.createElement('div'),
2595 toolbox_left: document.createElement('div'),
2596 toolbox_right: document.createElement('div'),
2597 toolbox_reset: document.createElement('div'),
2598 toolbox_zoomin: document.createElement('div'),
2599 toolbox_zoomout: document.createElement('div'),
2600 toolbox_volume: document.createElement('div'),
2601 title_date: document.createElement('span'),
2602 title_time: document.createElement('span'),
2603 title_units: document.createElement('span'),
2604 perfect_scroller: document.createElement('div'),
2608 this.element_legend.innerHTML = '';
2610 if(this.library.toolboxPanAndZoom !== null) {
2612 var get_pan_and_zoom_step = function(event) {
2614 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2616 else if (event.shiftKey)
2617 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2619 else if (event.altKey)
2620 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2623 return NETDATA.options.current.pan_and_zoom_factor;
2626 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2627 this.element.appendChild(this.element_legend_childs.toolbox);
2629 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2630 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2631 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2632 this.element_legend_childs.toolbox_left.onclick = function(e) {
2635 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2636 var before = that.view_before - step;
2637 var after = that.view_after - step;
2638 if(after >= that.netdata_first)
2639 that.library.toolboxPanAndZoom(that, after, before);
2641 if(NETDATA.options.current.show_help === true)
2642 $(this.element_legend_childs.toolbox_left).popover({
2647 placement: 'bottom',
2648 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2650 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>'
2654 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2655 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2656 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2657 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2659 NETDATA.resetAllCharts(that);
2661 if(NETDATA.options.current.show_help === true)
2662 $(this.element_legend_childs.toolbox_reset).popover({
2667 placement: 'bottom',
2668 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2669 title: 'Chart Reset',
2670 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>'
2673 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2674 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2675 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2676 this.element_legend_childs.toolbox_right.onclick = function(e) {
2678 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2679 var before = that.view_before + step;
2680 var after = that.view_after + step;
2681 if(before <= that.netdata_last)
2682 that.library.toolboxPanAndZoom(that, after, before);
2684 if(NETDATA.options.current.show_help === true)
2685 $(this.element_legend_childs.toolbox_right).popover({
2690 placement: 'bottom',
2691 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2693 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>'
2697 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2698 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2699 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2700 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2702 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2703 var before = that.view_before - dt;
2704 var after = that.view_after + dt;
2705 that.library.toolboxPanAndZoom(that, after, before);
2707 if(NETDATA.options.current.show_help === true)
2708 $(this.element_legend_childs.toolbox_zoomin).popover({
2713 placement: 'bottom',
2714 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2715 title: 'Chart Zoom In',
2716 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>'
2719 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2720 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2721 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2722 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2724 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);
2725 var before = that.view_before + dt;
2726 var after = that.view_after - dt;
2728 that.library.toolboxPanAndZoom(that, after, before);
2730 if(NETDATA.options.current.show_help === true)
2731 $(this.element_legend_childs.toolbox_zoomout).popover({
2736 placement: 'bottom',
2737 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2738 title: 'Chart Zoom Out',
2739 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>'
2742 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2743 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2744 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2745 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2746 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2747 //e.preventDefault();
2748 //alert('clicked toolbox_volume on ' + that.id);
2752 this.element_legend_childs.toolbox = null;
2753 this.element_legend_childs.toolbox_left = null;
2754 this.element_legend_childs.toolbox_reset = null;
2755 this.element_legend_childs.toolbox_right = null;
2756 this.element_legend_childs.toolbox_zoomin = null;
2757 this.element_legend_childs.toolbox_zoomout = null;
2758 this.element_legend_childs.toolbox_volume = null;
2761 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2762 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2763 this.element.appendChild(this.element_legend_childs.resize_handler);
2764 if(NETDATA.options.current.show_help === true)
2765 $(this.element_legend_childs.resize_handler).popover({
2770 placement: 'bottom',
2771 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2772 title: 'Chart Resize',
2773 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>'
2777 this.element_legend_childs.resize_handler.onmousedown =
2779 that.resizeHandler(e);
2783 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2784 that.resizeHandler(e);
2787 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2788 this.element_legend.appendChild(this.element_legend_childs.title_date);
2789 this.__last_shown_legend_date = undefined;
2791 this.element_legend.appendChild(document.createElement('br'));
2793 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2794 this.element_legend.appendChild(this.element_legend_childs.title_time);
2795 this.__last_shown_legend_time = undefined;
2797 this.element_legend.appendChild(document.createElement('br'));
2799 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2800 this.element_legend.appendChild(this.element_legend_childs.title_units);
2801 this.__last_shown_legend_units = undefined;
2803 this.element_legend.appendChild(document.createElement('br'));
2805 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2806 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2808 content.className = 'netdata-legend-series-content';
2809 this.element_legend_childs.perfect_scroller.appendChild(content);
2811 if(NETDATA.options.current.show_help === true)
2812 $(content).popover({
2817 placement: 'bottom',
2818 title: 'Chart Legend',
2819 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2820 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>'
2824 this.element_legend_childs = {
2826 resize_handler: null,
2829 toolbox_right: null,
2830 toolbox_reset: null,
2831 toolbox_zoomin: null,
2832 toolbox_zoomout: null,
2833 toolbox_volume: null,
2837 perfect_scroller: null,
2843 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2844 if(this.debug === true)
2845 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2847 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2848 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2853 keys = Object.keys(this.chart.dimensions);
2854 for(i = 0, len = keys.length; i < len ;i++) {
2856 tmp.push(this.chart.dimensions[dim].name);
2857 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2859 this.element_legend_childs.series.labels_key = tmp.toString();
2860 if(this.debug === true)
2861 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2864 // create a hidden div to be used for hidding
2865 // the original legend of the chart library
2866 var el = document.createElement('div');
2867 if(this.element_legend !== null)
2868 this.element_legend.appendChild(el);
2869 el.style.display = 'none';
2871 this.element_legend_childs.hidden = document.createElement('div');
2872 el.appendChild(this.element_legend_childs.hidden);
2874 if(this.element_legend_childs.perfect_scroller !== null) {
2875 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2877 wheelPropagation: true,
2878 swipePropagation: true,
2879 minScrollbarLength: null,
2880 maxScrollbarLength: null,
2881 useBothWheelAxes: false,
2882 suppressScrollX: true,
2883 suppressScrollY: false,
2884 scrollXMarginOffset: 0,
2885 scrollYMarginOffset: 0,
2888 Ps.update(this.element_legend_childs.perfect_scroller);
2891 this.legendShowLatestValues();
2894 this.hasLegend = function() {
2895 if(typeof this.___hasLegendCache___ !== 'undefined')
2896 return this.___hasLegendCache___;
2899 if(this.library && this.library.legend(this) === 'right-side') {
2900 var legend = $(this.element).data('legend') || 'yes';
2901 if(legend === 'yes') leg = true;
2904 this.___hasLegendCache___ = leg;
2908 this.legendWidth = function() {
2909 return (this.hasLegend())?140:0;
2912 this.legendHeight = function() {
2913 return $(this.element).height();
2916 this.chartWidth = function() {
2917 return $(this.element).width() - this.legendWidth();
2920 this.chartHeight = function() {
2921 return $(this.element).height();
2924 this.chartPixelsPerPoint = function() {
2925 // force an options provided detail
2926 var px = this.pixels_per_point;
2928 if(this.library && px < this.library.pixels_per_point(this))
2929 px = this.library.pixels_per_point(this);
2931 if(px < NETDATA.options.current.pixels_per_point)
2932 px = NETDATA.options.current.pixels_per_point;
2937 this.needsRecreation = function() {
2939 this.chart_created === true
2941 && this.library.autoresize() === false
2942 && this.tm.last_resized < NETDATA.options.last_resized
2946 this.chartURL = function() {
2947 var after, before, points_multiplier = 1;
2948 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2949 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2951 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2952 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2953 this.view_after = after * 1000;
2954 this.view_before = before * 1000;
2956 this.requested_padding = null;
2957 points_multiplier = 1;
2959 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2960 this.tm.pan_and_zoom_seq = 0;
2962 before = Math.round(this.current.force_before_ms / 1000);
2963 after = Math.round(this.current.force_after_ms / 1000);
2964 this.view_after = after * 1000;
2965 this.view_before = before * 1000;
2967 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2968 this.requested_padding = Math.round((before - after) / 2);
2969 after -= this.requested_padding;
2970 before += this.requested_padding;
2971 this.requested_padding *= 1000;
2972 points_multiplier = 2;
2975 this.current.force_before_ms = null;
2976 this.current.force_after_ms = null;
2979 this.tm.pan_and_zoom_seq = 0;
2981 before = this.before;
2983 this.view_after = after * 1000;
2984 this.view_before = before * 1000;
2986 this.requested_padding = null;
2987 points_multiplier = 1;
2990 this.requested_after = after * 1000;
2991 this.requested_before = before * 1000;
2993 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2995 // build the data URL
2996 this.data_url = this.host + this.chart.data_url;
2997 this.data_url += "&format=" + this.library.format();
2998 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2999 this.data_url += "&group=" + this.method;
3001 if(this.override_options !== null)
3002 this.data_url += "&options=" + this.override_options.toString();
3004 this.data_url += "&options=" + this.library.options(this);
3006 this.data_url += '|jsonwrap';
3008 if(NETDATA.options.current.eliminate_zero_dimensions === true)
3009 this.data_url += '|nonzero';
3011 if(this.append_options !== null)
3012 this.data_url += '|' + this.append_options.toString();
3015 this.data_url += "&after=" + after.toString();
3018 this.data_url += "&before=" + before.toString();
3021 this.data_url += "&dimensions=" + this.dimensions;
3023 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3024 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3027 this.redrawChart = function() {
3028 if(this.data !== null)
3029 this.updateChartWithData(this.data);
3032 this.updateChartWithData = function(data) {
3033 if(this.debug === true)
3034 this.log('updateChartWithData() called.');
3036 // this may force the chart to be re-created
3040 this.updates_counter++;
3041 this.updates_since_last_unhide++;
3042 this.updates_since_last_creation++;
3044 var started = Date.now();
3046 // if the result is JSON, find the latest update-every
3047 this.data_update_every = data.view_update_every * 1000;
3048 this.data_after = data.after * 1000;
3049 this.data_before = data.before * 1000;
3050 this.netdata_first = data.first_entry * 1000;
3051 this.netdata_last = data.last_entry * 1000;
3052 this.data_points = data.points;
3055 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3056 if(this.view_after < this.data_after) {
3057 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3058 this.view_after = this.data_after;
3061 if(this.view_before > this.data_before) {
3062 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3063 this.view_before = this.data_before;
3067 this.view_after = this.data_after;
3068 this.view_before = this.data_before;
3071 if(this.debug === true) {
3072 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3074 if(this.current.force_after_ms)
3075 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3077 this.log('STATUS: forced : unset');
3079 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3080 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3081 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3082 this.log('STATUS: points : ' + (this.data_points).toString());
3085 if(this.data_points === 0) {
3090 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3091 if(this.debug === true)
3092 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3094 this.chart_created = false;
3097 // check and update the legend
3098 this.legendUpdateDOM();
3100 if(this.chart_created === true
3101 && typeof this.library.update === 'function') {
3103 if(this.debug === true)
3104 this.log('updating chart...');
3106 if(callChartLibraryUpdateSafely(data) === false)
3110 if(this.debug === true)
3111 this.log('creating chart...');
3113 if(callChartLibraryCreateSafely(data) === false)
3117 this.legendShowLatestValues();
3118 if(this.selected === true)
3119 NETDATA.globalSelectionSync.stop();
3121 // update the performance counters
3122 var now = Date.now();
3123 this.tm.last_updated = now;
3125 // don't update last_autorefreshed if this chart is
3126 // forced to be updated with global PanAndZoom
3127 if(NETDATA.globalPanAndZoom.isActive())
3128 this.tm.last_autorefreshed = 0;
3130 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3131 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3133 this.tm.last_autorefreshed = now;
3136 this.refresh_dt_ms = now - started;
3137 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3139 if(this.refresh_dt_element !== null)
3140 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3143 this.updateChart = function(callback) {
3144 if(this.debug === true)
3145 this.log('updateChart() called.');
3147 if(this._updating === true) {
3148 if(this.debug === true)
3149 this.log('I am already updating...');
3151 if(typeof callback === 'function')
3157 // due to late initialization of charts and libraries
3158 // we need to check this too
3159 if(this.enabled === false) {
3160 if(this.debug === true)
3161 this.log('I am not enabled');
3163 if(typeof callback === 'function')
3169 if(canBeRendered() === false) {
3170 if(typeof callback === 'function')
3176 if(this.chart === null)
3177 return this.getChart(function() {
3178 return that.updateChart(callback);
3181 if(this.library.initialized === false) {
3182 if(this.library.enabled === true) {
3183 return this.library.initialize(function () {
3184 return that.updateChart(callback);
3188 error('chart library "' + this.library_name + '" is not available.');
3190 if(typeof callback === 'function')
3197 this.clearSelection();
3200 if(this.debug === true)
3201 this.log('updating from ' + this.data_url);
3203 NETDATA.statistics.refreshes_total++;
3204 NETDATA.statistics.refreshes_active++;
3206 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3207 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3209 this._updating = true;
3211 this.xhr = $.ajax( {
3216 'Cache-Control': 'no-cache, no-store',
3217 'Pragma': 'no-cache'
3219 xhrFields: { withCredentials: true } // required for the cookie
3221 .done(function(data) {
3222 that.xhr = undefined;
3223 that.retries_on_data_failures = 0;
3225 if(that.debug === true)
3226 that.log('data received. updating chart.');
3228 that.updateChartWithData(data);
3230 .fail(function(msg) {
3231 that.xhr = undefined;
3233 if(msg.statusText !== 'abort') {
3234 that.retries_on_data_failures++;
3235 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3236 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3237 that.retries_on_data_failures = 0;
3238 error('data download failed for url: ' + that.data_url);
3241 that.tm.last_autorefreshed = Date.now();
3242 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3246 .always(function() {
3247 that.xhr = undefined;
3249 NETDATA.statistics.refreshes_active--;
3250 that._updating = false;
3252 if(typeof callback === 'function')
3257 this.isVisible = function(nocache) {
3258 if(typeof nocache === 'undefined')
3261 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3263 // caching - we do not evaluate the charts visibility
3264 // if the page has not been scrolled since the last check
3265 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3266 return this.___isVisible___;
3268 this.tm.last_visible_check = Date.now();
3270 var wh = window.innerHeight;
3271 var x = this.element.getBoundingClientRect();
3275 if(x.width === 0 || x.height === 0) {
3277 this.___isVisible___ = false;
3278 return this.___isVisible___;
3281 if(x.top < 0 && -x.top > x.height) {
3282 // the chart is entirely above
3283 ret = -x.top - x.height;
3285 else if(x.top > wh) {
3286 // the chart is entirely below
3290 if(ret > tolerance) {
3291 // the chart is too far
3294 this.___isVisible___ = false;
3295 return this.___isVisible___;
3298 // the chart is inside or very close
3301 this.___isVisible___ = true;
3302 return this.___isVisible___;
3306 this.isAutoRefreshable = function() {
3307 return (this.current.autorefresh);
3310 this.canBeAutoRefreshed = function() {
3311 var now = Date.now();
3313 if(this.running === true) {
3314 if(this.debug === true)
3315 this.log('I am already running');
3320 if(this.enabled === false) {
3321 if(this.debug === true)
3322 this.log('I am not enabled');
3327 if(this.library === null || this.library.enabled === false) {
3328 error('charting library "' + this.library_name + '" is not available');
3329 if(this.debug === true)
3330 this.log('My chart library ' + this.library_name + ' is not available');
3335 if(this.isVisible() === false) {
3336 if(NETDATA.options.debug.visibility === true || this.debug === true)
3337 this.log('I am not visible');
3342 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3343 if(this.debug === true)
3344 this.log('timed force update detected - allowing this update');
3346 this.current.force_update_at = 0;
3350 if(this.isAutoRefreshable() === true) {
3351 // allow the first update, even if the page is not visible
3352 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3353 if(NETDATA.options.debug.focus === true || this.debug === true)
3354 this.log('canBeAutoRefreshed(): page does not have focus');
3359 if(this.needsRecreation() === true) {
3360 if(this.debug === true)
3361 this.log('canBeAutoRefreshed(): needs re-creation.');
3366 // options valid only for autoRefresh()
3367 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3368 if(NETDATA.globalPanAndZoom.isActive()) {
3369 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3370 if(this.debug === true)
3371 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3376 if(this.debug === true)
3377 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3383 if(this.selected === true) {
3384 if(this.debug === true)
3385 this.log('canBeAutoRefreshed(): I have a selection in place.');
3390 if(this.paused === true) {
3391 if(this.debug === true)
3392 this.log('canBeAutoRefreshed(): I am paused.');
3397 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3398 if(this.debug === true)
3399 this.log('canBeAutoRefreshed(): It is time to update me.');
3409 this.autoRefresh = function(callback) {
3410 if(this.canBeAutoRefreshed() === true && this.running === false) {
3413 state.running = true;
3414 state.updateChart(function() {
3415 state.running = false;
3417 if(typeof callback !== 'undefined')
3422 if(typeof callback !== 'undefined')
3427 this._defaultsFromDownloadedChart = function(chart) {
3429 this.chart_url = chart.url;
3430 this.data_update_every = chart.update_every * 1000;
3431 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3432 this.tm.last_info_downloaded = Date.now();
3434 if(this.title === null)
3435 this.title = chart.title;
3437 if(this.units === null)
3438 this.units = chart.units;
3441 // fetch the chart description from the netdata server
3442 this.getChart = function(callback) {
3443 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3445 this._defaultsFromDownloadedChart(this.chart);
3447 if(typeof callback === 'function')
3451 this.chart_url = "/api/v1/chart?chart=" + this.id;
3453 if(this.debug === true)
3454 this.log('downloading ' + this.chart_url);
3457 url: this.host + this.chart_url,
3460 xhrFields: { withCredentials: true } // required for the cookie
3462 .done(function(chart) {
3463 chart.url = that.chart_url;
3464 that._defaultsFromDownloadedChart(chart);
3465 NETDATA.chartRegistry.add(that.host, that.id, chart);
3468 NETDATA.error(404, that.chart_url);
3469 error('chart not found on url "' + that.chart_url + '"');
3471 .always(function() {
3472 if(typeof callback === 'function')
3478 // ============================================================================================================
3484 NETDATA.resetAllCharts = function(state) {
3485 // first clear the global selection sync
3486 // to make sure no chart is in selected state
3487 state.globalSelectionSyncStop();
3489 // there are 2 possibilities here
3490 // a. state is the global Pan and Zoom master
3491 // b. state is not the global Pan and Zoom master
3493 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3496 // clear the global Pan and Zoom
3497 // this will also refresh the master
3498 // and unblock any charts currently mirroring the master
3499 NETDATA.globalPanAndZoom.clearMaster();
3501 // if we were not the master, reset our status too
3502 // this is required because most probably the mouse
3503 // is over this chart, blocking it from auto-refreshing
3504 if(master === false && (state.paused === true || state.selected === true))
3508 // get or create a chart state, given a DOM element
3509 NETDATA.chartState = function(element) {
3510 var state = $(element).data('netdata-state-object') || null;
3511 if(state === null) {
3512 state = new chartState(element);
3513 $(element).data('netdata-state-object', state);
3518 // ----------------------------------------------------------------------------------------------------------------
3519 // Library functions
3521 // Load a script without jquery
3522 // This is used to load jquery - after it is loaded, we use jquery
3523 NETDATA._loadjQuery = function(callback) {
3524 if(typeof jQuery === 'undefined') {
3525 if(NETDATA.options.debug.main_loop === true)
3526 console.log('loading ' + NETDATA.jQuery);
3528 var script = document.createElement('script');
3529 script.type = 'text/javascript';
3530 script.async = true;
3531 script.src = NETDATA.jQuery;
3533 // script.onabort = onError;
3534 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3535 if(typeof callback === "function")
3536 script.onload = callback;
3538 var s = document.getElementsByTagName('script')[0];
3539 s.parentNode.insertBefore(script, s);
3541 else if(typeof callback === "function")
3545 NETDATA._loadCSS = function(filename) {
3546 // don't use jQuery here
3547 // styles are loaded before jQuery
3548 // to eliminate showing an unstyled page to the user
3550 var fileref = document.createElement("link");
3551 fileref.setAttribute("rel", "stylesheet");
3552 fileref.setAttribute("type", "text/css");
3553 fileref.setAttribute("href", filename);
3555 if (typeof fileref !== 'undefined')
3556 document.getElementsByTagName("head")[0].appendChild(fileref);
3559 NETDATA.colorHex2Rgb = function(hex) {
3560 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3561 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3562 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3563 return r + r + g + g + b + b;
3566 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3568 r: parseInt(result[1], 16),
3569 g: parseInt(result[2], 16),
3570 b: parseInt(result[3], 16)
3574 NETDATA.colorLuminance = function(hex, lum) {
3575 // validate hex string
3576 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3578 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3582 // convert to decimal and change luminosity
3583 var rgb = "#", c, i;
3584 for (i = 0; i < 3; i++) {
3585 c = parseInt(hex.substr(i*2,2), 16);
3586 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3587 rgb += ("00"+c).substr(c.length);
3593 NETDATA.guid = function() {
3595 return Math.floor((1 + Math.random()) * 0x10000)
3600 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3603 NETDATA.zeropad = function(x) {
3604 if(x > -10 && x < 10) return '0' + x.toString();
3605 else return x.toString();
3608 // user function to signal us the DOM has been
3610 NETDATA.updatedDom = function() {
3611 NETDATA.options.updated_dom = true;
3614 NETDATA.ready = function(callback) {
3615 NETDATA.options.pauseCallback = callback;
3618 NETDATA.pause = function(callback) {
3619 if(typeof callback === 'function') {
3620 if (NETDATA.options.pause === true)
3623 NETDATA.options.pauseCallback = callback;
3627 NETDATA.unpause = function() {
3628 NETDATA.options.pauseCallback = null;
3629 NETDATA.options.updated_dom = true;
3630 NETDATA.options.pause = false;
3633 // ----------------------------------------------------------------------------------------------------------------
3635 // this is purely sequential charts refresher
3636 // it is meant to be autonomous
3637 NETDATA.chartRefresherNoParallel = function(index) {
3638 if(NETDATA.options.debug.main_loop === true)
3639 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3641 if(NETDATA.options.updated_dom === true) {
3642 // the dom has been updated
3643 // get the dom parts again
3644 NETDATA.parseDom(NETDATA.chartRefresher);
3647 if(index >= NETDATA.options.targets.length) {
3648 if(NETDATA.options.debug.main_loop === true)
3649 console.log('waiting to restart main loop...');
3651 NETDATA.options.auto_refresher_fast_weight = 0;
3653 setTimeout(function() {
3654 NETDATA.chartRefresher();
3655 }, NETDATA.options.current.idle_between_loops);
3658 var state = NETDATA.options.targets[index];
3660 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3661 if(NETDATA.options.debug.main_loop === true)
3662 console.log('fast rendering...');
3664 state.autoRefresh(function() {
3665 NETDATA.chartRefresherNoParallel(++index);
3669 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3670 NETDATA.options.auto_refresher_fast_weight = 0;
3672 setTimeout(function() {
3673 state.autoRefresh(function() {
3674 NETDATA.chartRefresherNoParallel(++index);
3676 }, NETDATA.options.current.idle_between_charts);
3681 NETDATA.chartRefresherWaitTime = function() {
3682 return NETDATA.options.current.idle_parallel_loops;
3685 // the default refresher
3686 NETDATA.chartRefresher = function() {
3687 // console.log('auto-refresher...');
3689 if(NETDATA.options.pause === true) {
3690 // console.log('auto-refresher is paused');
3691 setTimeout(NETDATA.chartRefresher,
3692 NETDATA.chartRefresherWaitTime());
3696 if(typeof NETDATA.options.pauseCallback === 'function') {
3697 // console.log('auto-refresher is calling pauseCallback');
3698 NETDATA.options.pause = true;
3699 NETDATA.options.pauseCallback();
3700 NETDATA.chartRefresher();
3704 if(NETDATA.options.current.parallel_refresher === false) {
3705 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3706 NETDATA.chartRefresherNoParallel(0);
3710 if(NETDATA.options.updated_dom === true) {
3711 // the dom has been updated
3712 // get the dom parts again
3713 // console.log('auto-refresher is calling parseDom()');
3714 NETDATA.parseDom(NETDATA.chartRefresher);
3719 var targets = NETDATA.options.targets;
3720 var len = targets.length;
3723 state = targets[len];
3724 if(state.isVisible() === false || state.running === true)
3727 if(state.library.initialized === false) {
3728 if(state.library.enabled === true) {
3729 state.library.initialize(NETDATA.chartRefresher);
3733 state.error('chart library "' + state.library_name + '" is not enabled.');
3737 parallel.unshift(state);
3740 if(parallel.length > 0) {
3741 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3742 // this will execute the jobs in parallel
3743 $(parallel).each(function() {
3748 // console.log('auto-refresher nothing to do');
3751 // run the next refresh iteration
3752 setTimeout(NETDATA.chartRefresher,
3753 NETDATA.chartRefresherWaitTime());
3756 NETDATA.parseDom = function(callback) {
3757 NETDATA.options.last_page_scroll = Date.now();
3758 NETDATA.options.updated_dom = false;
3760 var targets = $('div[data-netdata]'); //.filter(':visible');
3762 if(NETDATA.options.debug.main_loop === true)
3763 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3765 NETDATA.options.targets = [];
3766 var len = targets.length;
3768 // the initialization will take care of sizing
3769 // and the "loading..." message
3770 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3773 if(typeof callback === 'function')
3777 // this is the main function - where everything starts
3778 NETDATA.start = function() {
3779 // this should be called only once
3781 NETDATA.options.page_is_visible = true;
3783 $(window).blur(function() {
3784 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3785 NETDATA.options.page_is_visible = false;
3786 if(NETDATA.options.debug.focus === true)
3787 console.log('Lost Focus!');
3791 $(window).focus(function() {
3792 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3793 NETDATA.options.page_is_visible = true;
3794 if(NETDATA.options.debug.focus === true)
3795 console.log('Focus restored!');
3799 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3800 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3801 NETDATA.options.page_is_visible = false;
3802 if(NETDATA.options.debug.focus === true)
3803 console.log('Document has no focus!');
3807 // bootstrap tab switching
3808 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3810 // bootstrap modal switching
3811 var $modal = $('.modal');
3812 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3813 $modal.on('shown.bs.modal', NETDATA.onscroll);
3815 // bootstrap collapse switching
3816 var $collapse = $('.collapse');
3817 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3818 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3820 NETDATA.parseDom(NETDATA.chartRefresher);
3822 // Alarms initialization
3823 setTimeout(NETDATA.alarms.init, 1000);
3825 // Registry initialization
3826 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3828 if(typeof netdataCallback === 'function')
3832 // ----------------------------------------------------------------------------------------------------------------
3835 NETDATA.peityInitialize = function(callback) {
3836 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3838 url: NETDATA.peity_js,
3841 xhrFields: { withCredentials: true } // required for the cookie
3844 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3847 NETDATA.chartLibraries.peity.enabled = false;
3848 NETDATA.error(100, NETDATA.peity_js);
3850 .always(function() {
3851 if(typeof callback === "function")
3856 NETDATA.chartLibraries.peity.enabled = false;
3857 if(typeof callback === "function")
3862 NETDATA.peityChartUpdate = function(state, data) {
3863 state.peity_instance.innerHTML = data.result;
3865 if(state.peity_options.stroke !== state.chartColors()[0]) {
3866 state.peity_options.stroke = state.chartColors()[0];
3867 if(state.chart.chart_type === 'line')
3868 state.peity_options.fill = NETDATA.themes.current.background;
3870 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3873 $(state.peity_instance).peity('line', state.peity_options);
3877 NETDATA.peityChartCreate = function(state, data) {
3878 state.peity_instance = document.createElement('div');
3879 state.element_chart.appendChild(state.peity_instance);
3881 var self = $(state.element);
3882 state.peity_options = {
3883 stroke: NETDATA.themes.current.foreground,
3884 strokeWidth: self.data('peity-strokewidth') || 1,
3885 width: state.chartWidth(),
3886 height: state.chartHeight(),
3887 fill: NETDATA.themes.current.foreground
3890 NETDATA.peityChartUpdate(state, data);
3894 // ----------------------------------------------------------------------------------------------------------------
3897 NETDATA.sparklineInitialize = function(callback) {
3898 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3900 url: NETDATA.sparkline_js,
3903 xhrFields: { withCredentials: true } // required for the cookie
3906 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3909 NETDATA.chartLibraries.sparkline.enabled = false;
3910 NETDATA.error(100, NETDATA.sparkline_js);
3912 .always(function() {
3913 if(typeof callback === "function")
3918 NETDATA.chartLibraries.sparkline.enabled = false;
3919 if(typeof callback === "function")
3924 NETDATA.sparklineChartUpdate = function(state, data) {
3925 state.sparkline_options.width = state.chartWidth();
3926 state.sparkline_options.height = state.chartHeight();
3928 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3932 NETDATA.sparklineChartCreate = function(state, data) {
3933 var self = $(state.element);
3934 var type = self.data('sparkline-type') || 'line';
3935 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3936 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3937 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3938 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3939 var composite = self.data('sparkline-composite') || undefined;
3940 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3941 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3942 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3943 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3944 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3945 var spotColor = self.data('sparkline-spotcolor') || undefined;
3946 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3947 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3948 var spotRadius = self.data('sparkline-spotradius') || undefined;
3949 var valueSpots = self.data('sparkline-valuespots') || undefined;
3950 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3951 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3952 var lineWidth = self.data('sparkline-linewidth') || undefined;
3953 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3954 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3955 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3956 var xvalues = self.data('sparkline-xvalues') || undefined;
3957 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3958 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3959 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3960 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3961 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3962 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3963 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3964 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3965 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3966 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3967 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3968 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3969 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3970 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3971 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3972 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3973 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3974 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3975 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3976 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3977 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3978 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3980 if(spotColor === 'disable') spotColor='';
3981 if(minSpotColor === 'disable') minSpotColor='';
3982 if(maxSpotColor === 'disable') maxSpotColor='';
3984 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3986 state.sparkline_options = {
3988 lineColor: lineColor,
3989 fillColor: fillColor,
3990 chartRangeMin: chartRangeMin,
3991 chartRangeMax: chartRangeMax,
3992 composite: composite,
3993 enableTagOptions: enableTagOptions,
3994 tagOptionPrefix: tagOptionPrefix,
3995 tagValuesAttribute: tagValuesAttribute,
3996 disableHiddenCheck: disableHiddenCheck,
3997 defaultPixelsPerValue: defaultPixelsPerValue,
3998 spotColor: spotColor,
3999 minSpotColor: minSpotColor,
4000 maxSpotColor: maxSpotColor,
4001 spotRadius: spotRadius,
4002 valueSpots: valueSpots,
4003 highlightSpotColor: highlightSpotColor,
4004 highlightLineColor: highlightLineColor,
4005 lineWidth: lineWidth,
4006 normalRangeMin: normalRangeMin,
4007 normalRangeMax: normalRangeMax,
4008 drawNormalOnTop: drawNormalOnTop,
4010 chartRangeClip: chartRangeClip,
4011 chartRangeMinX: chartRangeMinX,
4012 chartRangeMaxX: chartRangeMaxX,
4013 disableInteraction: disableInteraction,
4014 disableTooltips: disableTooltips,
4015 disableHighlight: disableHighlight,
4016 highlightLighten: highlightLighten,
4017 highlightColor: highlightColor,
4018 tooltipContainer: tooltipContainer,
4019 tooltipClassname: tooltipClassname,
4020 tooltipChartTitle: state.title,
4021 tooltipFormat: tooltipFormat,
4022 tooltipPrefix: tooltipPrefix,
4023 tooltipSuffix: tooltipSuffix,
4024 tooltipSkipNull: tooltipSkipNull,
4025 tooltipValueLookups: tooltipValueLookups,
4026 tooltipFormatFieldlist: tooltipFormatFieldlist,
4027 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4028 numberFormatter: numberFormatter,
4029 numberDigitGroupSep: numberDigitGroupSep,
4030 numberDecimalMark: numberDecimalMark,
4031 numberDigitGroupCount: numberDigitGroupCount,
4032 animatedZooms: animatedZooms,
4033 width: state.chartWidth(),
4034 height: state.chartHeight()
4037 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4041 // ----------------------------------------------------------------------------------------------------------------
4048 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4049 if(after < state.netdata_first)
4050 after = state.netdata_first;
4052 if(before > state.netdata_last)
4053 before = state.netdata_last;
4055 state.setMode('zoom');
4056 state.globalSelectionSyncStop();
4057 state.globalSelectionSyncDelay();
4058 state.dygraph_user_action = true;
4059 state.dygraph_force_zoom = true;
4060 state.updateChartPanOrZoom(after, before);
4061 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4064 NETDATA.dygraphSetSelection = function(state, t) {
4065 if(typeof state.dygraph_instance !== 'undefined') {
4066 var r = state.calculateRowForTime(t);
4068 state.dygraph_instance.setSelection(r);
4070 state.dygraph_instance.clearSelection();
4071 state.legendShowUndefined();
4078 NETDATA.dygraphClearSelection = function(state) {
4079 if(typeof state.dygraph_instance !== 'undefined') {
4080 state.dygraph_instance.clearSelection();
4085 NETDATA.dygraphSmoothInitialize = function(callback) {
4087 url: NETDATA.dygraph_smooth_js,
4090 xhrFields: { withCredentials: true } // required for the cookie
4093 NETDATA.dygraph.smooth = true;
4094 smoothPlotter.smoothing = 0.3;
4097 NETDATA.dygraph.smooth = false;
4099 .always(function() {
4100 if(typeof callback === "function")
4105 NETDATA.dygraphInitialize = function(callback) {
4106 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4108 url: NETDATA.dygraph_js,
4111 xhrFields: { withCredentials: true } // required for the cookie
4114 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4117 NETDATA.chartLibraries.dygraph.enabled = false;
4118 NETDATA.error(100, NETDATA.dygraph_js);
4120 .always(function() {
4121 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4122 NETDATA.dygraphSmoothInitialize(callback);
4123 else if(typeof callback === "function")
4128 NETDATA.chartLibraries.dygraph.enabled = false;
4129 if(typeof callback === "function")
4134 NETDATA.dygraphChartUpdate = function(state, data) {
4135 var dygraph = state.dygraph_instance;
4137 if(typeof dygraph === 'undefined')
4138 return NETDATA.dygraphChartCreate(state, data);
4140 // when the chart is not visible, and hidden
4141 // if there is a window resize, dygraph detects
4142 // its element size as 0x0.
4143 // this will make it re-appear properly
4145 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4149 file: data.result.data,
4150 colors: state.chartColors(),
4151 labels: data.result.labels,
4152 labelsDivWidth: state.chartWidth() - 70,
4153 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4156 if(state.dygraph_force_zoom === true) {
4157 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4158 state.log('dygraphChartUpdate() forced zoom update');
4160 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4161 options.isZoomedIgnoreProgrammaticZoom = true;
4162 state.dygraph_force_zoom = false;
4164 else if(state.current.name !== 'auto') {
4165 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4166 state.log('dygraphChartUpdate() loose update');
4169 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4170 state.log('dygraphChartUpdate() strict update');
4172 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4173 options.isZoomedIgnoreProgrammaticZoom = true;
4176 options.valueRange = state.dygraph_options.valueRange;
4178 var oldMax = null, oldMin = null;
4179 if(state.__commonMin !== null) {
4180 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4181 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4183 if(state.__commonMax !== null) {
4184 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4185 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4188 if(state.dygraph_smooth_eligible === true) {
4189 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4190 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4191 NETDATA.dygraphChartCreate(state, data);
4196 dygraph.updateOptions(options);
4199 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4200 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4201 options.valueRange[0] = NETDATA.commonMin.get(state);
4204 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4205 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4206 options.valueRange[1] = NETDATA.commonMax.get(state);
4210 if(redraw === true) {
4211 // state.log('forcing redraw to adapt to common- min/max');
4212 dygraph.updateOptions(options);
4215 state.dygraph_last_rendered = Date.now();
4219 NETDATA.dygraphChartCreate = function(state, data) {
4220 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4221 state.log('dygraphChartCreate()');
4223 var self = $(state.element);
4225 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4226 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4228 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4230 var smooth = (NETDATA.dygraph.smooth === true)
4231 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4234 state.dygraph_options = {
4235 colors: self.data('dygraph-colors') || state.chartColors(),
4237 // leave a few pixels empty on the right of the chart
4238 rightGap: self.data('dygraph-rightgap')
4241 showRangeSelector: self.data('dygraph-showrangeselector')
4244 showRoller: self.data('dygraph-showroller')
4247 title: self.data('dygraph-title')
4250 titleHeight: self.data('dygraph-titleheight')
4253 legend: self.data('dygraph-legend')
4254 || 'always', // we need this to get selection events
4256 labels: data.result.labels,
4258 labelsDiv: self.data('dygraph-labelsdiv')
4259 || state.element_legend_childs.hidden,
4261 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4262 || { 'fontSize':'1px' },
4264 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4265 || state.chartWidth() - 70,
4267 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4270 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4276 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4279 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4282 includeZero: self.data('dygraph-includezero')
4283 || (chart_type === 'stacked'),
4285 xRangePad: self.data('dygraph-xrangepad')
4288 yRangePad: self.data('dygraph-yrangepad')
4291 valueRange: self.data('dygraph-valuerange')
4294 ylabel: state.units,
4296 yLabelWidth: self.data('dygraph-ylabelwidth')
4299 // the function to plot the chart
4302 // The width of the lines connecting data points.
4303 // This can be used to increase the contrast or some graphs.
4304 strokeWidth: self.data('dygraph-strokewidth')
4305 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4307 strokePattern: self.data('dygraph-strokepattern')
4310 // The size of the dot to draw on each point in pixels (see drawPoints).
4311 // A dot is always drawn when a point is "isolated",
4312 // i.e. there is a missing point on either side of it.
4313 // This also controls the size of those dots.
4314 drawPoints: self.data('dygraph-drawpoints')
4317 // Draw points at the edges of gaps in the data.
4318 // This improves visibility of small data segments or other data irregularities.
4319 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4322 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4325 pointSize: self.data('dygraph-pointsize')
4328 // enabling this makes the chart with little square lines
4329 stepPlot: self.data('dygraph-stepplot')
4332 // Draw a border around graph lines to make crossing lines more easily
4333 // distinguishable. Useful for graphs with many lines.
4334 strokeBorderColor: self.data('dygraph-strokebordercolor')
4335 || NETDATA.themes.current.background,
4337 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4338 || (chart_type === 'stacked')?0.0:0.0,
4340 fillGraph: self.data('dygraph-fillgraph')
4341 || (chart_type === 'area' || chart_type === 'stacked'),
4343 fillAlpha: self.data('dygraph-fillalpha')
4344 || ((chart_type === 'stacked')
4345 ?NETDATA.options.current.color_fill_opacity_stacked
4346 :NETDATA.options.current.color_fill_opacity_area),
4348 stackedGraph: self.data('dygraph-stackedgraph')
4349 || (chart_type === 'stacked'),
4351 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4354 drawAxis: self.data('dygraph-drawaxis')
4357 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4360 axisLineColor: self.data('dygraph-axislinecolor')
4361 || NETDATA.themes.current.axis,
4363 axisLineWidth: self.data('dygraph-axislinewidth')
4366 drawGrid: self.data('dygraph-drawgrid')
4369 gridLinePattern: self.data('dygraph-gridlinepattern')
4372 gridLineWidth: self.data('dygraph-gridlinewidth')
4375 gridLineColor: self.data('dygraph-gridlinecolor')
4376 || NETDATA.themes.current.grid,
4378 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4381 sigFigs: self.data('dygraph-sigfigs')
4384 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4387 valueFormatter: self.data('dygraph-valueformatter')
4388 || function(x){ return x.toFixed(2); },
4390 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4391 || highlightCircleSize,
4393 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4394 || null, // TOO SLOW: { strokeWidth: 1.5 },
4396 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4397 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4399 pointClickCallback: self.data('dygraph-pointclickcallback')
4402 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4406 ticker: Dygraph.dateTicker,
4407 axisLabelFormatter: function (d, gran) {
4409 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4411 valueFormatter: function (ms) {
4413 //var d = new Date(ms);
4414 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4416 // no need to return anything here
4423 valueFormatter: function (x) {
4424 // we format legends with the state object
4425 // no need to do anything here
4426 // return (Math.round(x*100) / 100).toLocaleString();
4427 // return state.legendFormatValue(x);
4432 legendFormatter: function(data) {
4433 var elements = state.element_legend_childs;
4435 // if the hidden div is not there
4436 // we are not managing the legend
4437 if(elements.hidden === null) return;
4439 if (typeof data.x !== 'undefined') {
4440 state.legendSetDate(data.x);
4441 var i = data.series.length;
4443 var series = data.series[i];
4444 if(series.isVisible === true)
4445 state.legendSetLabelValue(series.label, series.y);
4447 state.legendSetLabelValue(series.label, null);
4453 drawCallback: function(dygraph, is_initial) {
4454 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4455 state.dygraph_user_action = false;
4457 var x_range = dygraph.xAxisRange();
4458 var after = Math.round(x_range[0]);
4459 var before = Math.round(x_range[1]);
4461 if(NETDATA.options.debug.dygraph === true)
4462 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4464 if(before <= state.netdata_last && after >= state.netdata_first)
4465 state.updateChartPanOrZoom(after, before);
4468 zoomCallback: function(minDate, maxDate, yRanges) {
4471 if(NETDATA.options.debug.dygraph === true)
4472 state.log('dygraphZoomCallback()');
4474 state.globalSelectionSyncStop();
4475 state.globalSelectionSyncDelay();
4476 state.setMode('zoom');
4478 // refresh it to the greatest possible zoom level
4479 state.dygraph_user_action = true;
4480 state.dygraph_force_zoom = true;
4481 state.updateChartPanOrZoom(minDate, maxDate);
4483 highlightCallback: function(event, x, points, row, seriesName) {
4486 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4487 state.log('dygraphHighlightCallback()');
4491 // there is a bug in dygraph when the chart is zoomed enough
4492 // the time it thinks is selected is wrong
4493 // here we calculate the time t based on the row number selected
4495 // var t = state.data_after + row * state.data_update_every;
4496 // 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);
4498 state.globalSelectionSync(x);
4500 // fix legend zIndex using the internal structures of dygraph legend module
4501 // this works, but it is a hack!
4502 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4504 unhighlightCallback: function(event) {
4507 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4508 state.log('dygraphUnhighlightCallback()');
4510 state.unpauseChart();
4511 state.globalSelectionSyncStop();
4513 interactionModel : {
4514 mousedown: function(event, dygraph, context) {
4515 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4516 state.log('interactionModel.mousedown()');
4518 state.dygraph_user_action = true;
4519 state.globalSelectionSyncStop();
4521 if(NETDATA.options.debug.dygraph === true)
4522 state.log('dygraphMouseDown()');
4524 // Right-click should not initiate a zoom.
4525 if(event.button && event.button === 2) return;
4527 context.initializeMouseDown(event, dygraph, context);
4529 if(event.button && event.button === 1) {
4530 if (event.altKey || event.shiftKey) {
4531 state.setMode('pan');
4532 state.globalSelectionSyncDelay();
4533 Dygraph.startPan(event, dygraph, context);
4536 state.setMode('zoom');
4537 state.globalSelectionSyncDelay();
4538 Dygraph.startZoom(event, dygraph, context);
4542 if (event.altKey || event.shiftKey) {
4543 state.setMode('zoom');
4544 state.globalSelectionSyncDelay();
4545 Dygraph.startZoom(event, dygraph, context);
4548 state.setMode('pan');
4549 state.globalSelectionSyncDelay();
4550 Dygraph.startPan(event, dygraph, context);
4554 mousemove: function(event, dygraph, context) {
4555 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4556 state.log('interactionModel.mousemove()');
4558 if(context.isPanning) {
4559 state.dygraph_user_action = true;
4560 state.globalSelectionSyncStop();
4561 state.globalSelectionSyncDelay();
4562 state.setMode('pan');
4563 context.is2DPan = false;
4564 Dygraph.movePan(event, dygraph, context);
4566 else if(context.isZooming) {
4567 state.dygraph_user_action = true;
4568 state.globalSelectionSyncStop();
4569 state.globalSelectionSyncDelay();
4570 state.setMode('zoom');
4571 Dygraph.moveZoom(event, dygraph, context);
4574 mouseup: function(event, dygraph, context) {
4575 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4576 state.log('interactionModel.mouseup()');
4578 if (context.isPanning) {
4579 state.dygraph_user_action = true;
4580 state.globalSelectionSyncDelay();
4581 Dygraph.endPan(event, dygraph, context);
4583 else if (context.isZooming) {
4584 state.dygraph_user_action = true;
4585 state.globalSelectionSyncDelay();
4586 Dygraph.endZoom(event, dygraph, context);
4589 click: function(event, dygraph, context) {
4593 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4594 state.log('interactionModel.click()');
4596 event.preventDefault();
4598 dblclick: function(event, dygraph, context) {
4603 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4604 state.log('interactionModel.dblclick()');
4605 NETDATA.resetAllCharts(state);
4607 wheel: function(event, dygraph, context) {
4610 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4611 state.log('interactionModel.wheel()');
4613 // Take the offset of a mouse event on the dygraph canvas and
4614 // convert it to a pair of percentages from the bottom left.
4615 // (Not top left, bottom is where the lower value is.)
4616 function offsetToPercentage(g, offsetX, offsetY) {
4617 // This is calculating the pixel offset of the leftmost date.
4618 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4619 var yar0 = g.yAxisRange(0);
4621 // This is calculating the pixel of the highest value. (Top pixel)
4622 var yOffset = g.toDomCoords(null, yar0[1])[1];
4624 // x y w and h are relative to the corner of the drawing area,
4625 // so that the upper corner of the drawing area is (0, 0).
4626 var x = offsetX - xOffset;
4627 var y = offsetY - yOffset;
4629 // This is computing the rightmost pixel, effectively defining the
4631 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4633 // This is computing the lowest pixel, effectively defining the height.
4634 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4636 // Percentage from the left.
4637 var xPct = w === 0 ? 0 : (x / w);
4638 // Percentage from the top.
4639 var yPct = h === 0 ? 0 : (y / h);
4641 // The (1-) part below changes it from "% distance down from the top"
4642 // to "% distance up from the bottom".
4643 return [xPct, (1-yPct)];
4646 // Adjusts [x, y] toward each other by zoomInPercentage%
4647 // Split it so the left/bottom axis gets xBias/yBias of that change and
4648 // tight/top gets (1-xBias)/(1-yBias) of that change.
4650 // If a bias is missing it splits it down the middle.
4651 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4652 xBias = xBias || 0.5;
4653 yBias = yBias || 0.5;
4655 function adjustAxis(axis, zoomInPercentage, bias) {
4656 var delta = axis[1] - axis[0];
4657 var increment = delta * zoomInPercentage;
4658 var foo = [increment * bias, increment * (1-bias)];
4660 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4663 var yAxes = g.yAxisRanges();
4665 for (var i = 0; i < yAxes.length; i++) {
4666 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4669 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4672 if(event.altKey || event.shiftKey) {
4673 state.dygraph_user_action = true;
4675 state.globalSelectionSyncStop();
4676 state.globalSelectionSyncDelay();
4678 // http://dygraphs.com/gallery/interaction-api.js
4680 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4682 normal_def = event.wheelDelta / 40;
4685 normal_def = event.deltaY * -1.2;
4687 var normal = (event.detail) ? event.detail * -1 : normal_def;
4688 var percentage = normal / 50;
4690 if (!(event.offsetX && event.offsetY)){
4691 event.offsetX = event.layerX - event.target.offsetLeft;
4692 event.offsetY = event.layerY - event.target.offsetTop;
4695 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4696 var xPct = percentages[0];
4697 var yPct = percentages[1];
4699 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4700 var after = new_x_range[0];
4701 var before = new_x_range[1];
4703 var first = state.netdata_first + state.data_update_every;
4704 var last = state.netdata_last + state.data_update_every;
4707 after -= (before - last);
4714 state.setMode('zoom');
4715 if(state.updateChartPanOrZoom(after, before) === true)
4716 dygraph.updateOptions({ dateWindow: [ after, before ] });
4718 event.preventDefault();
4721 touchstart: function(event, dygraph, context) {
4722 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4723 state.log('interactionModel.touchstart()');
4725 state.dygraph_user_action = true;
4726 state.setMode('zoom');
4729 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4731 // we overwrite the touch directions at the end, to overwrite
4732 // the internal default of dygraph
4733 context.touchDirections = { x: true, y: false };
4735 state.dygraph_last_touch_start = Date.now();
4736 state.dygraph_last_touch_move = 0;
4738 if(typeof event.touches[0].pageX === 'number')
4739 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4741 state.dygraph_last_touch_page_x = 0;
4743 touchmove: function(event, dygraph, context) {
4744 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4745 state.log('interactionModel.touchmove()');
4747 state.dygraph_user_action = true;
4748 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4750 state.dygraph_last_touch_move = Date.now();
4752 touchend: function(event, dygraph, context) {
4753 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4754 state.log('interactionModel.touchend()');
4756 state.dygraph_user_action = true;
4757 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4759 // if it didn't move, it is a selection
4760 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4761 // internal api of dygraph
4762 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4763 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4764 if(NETDATA.dygraphSetSelection(state, t) === true)
4765 state.globalSelectionSync(t);
4768 // if it was double tap within double click time, reset the charts
4769 var now = Date.now();
4770 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4771 if(state.dygraph_last_touch_move === 0) {
4772 var dt = now - state.dygraph_last_touch_end;
4773 if(dt <= NETDATA.options.current.double_click_speed)
4774 NETDATA.resetAllCharts(state);
4778 // remember the timestamp of the last touch end
4779 state.dygraph_last_touch_end = now;
4784 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4785 state.dygraph_options.drawGrid = false;
4786 state.dygraph_options.drawAxis = false;
4787 state.dygraph_options.title = undefined;
4788 state.dygraph_options.ylabel = undefined;
4789 state.dygraph_options.yLabelWidth = 0;
4790 state.dygraph_options.labelsDivWidth = 120;
4791 state.dygraph_options.labelsDivStyles.width = '120px';
4792 state.dygraph_options.labelsSeparateLines = true;
4793 state.dygraph_options.rightGap = 0;
4794 state.dygraph_options.yRangePad = 1;
4797 if(smooth === true) {
4798 state.dygraph_smooth_eligible = true;
4800 if(NETDATA.options.current.smooth_plot === true)
4801 state.dygraph_options.plotter = smoothPlotter;
4803 else state.dygraph_smooth_eligible = false;
4805 state.dygraph_instance = new Dygraph(state.element_chart,
4806 data.result.data, state.dygraph_options);
4808 state.dygraph_force_zoom = false;
4809 state.dygraph_user_action = false;
4810 state.dygraph_last_rendered = Date.now();
4812 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4813 state.__commonMin = self.data('common-min') || null;
4814 state.__commonMax = self.data('common-max') || null;
4817 state.log('incompatible version of Dygraph detected');
4818 state.__commonMin = null;
4819 state.__commonMax = null;
4825 // ----------------------------------------------------------------------------------------------------------------
4828 NETDATA.morrisInitialize = function(callback) {
4829 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4831 // morris requires raphael
4832 if(!NETDATA.chartLibraries.raphael.initialized) {
4833 if(NETDATA.chartLibraries.raphael.enabled) {
4834 NETDATA.raphaelInitialize(function() {
4835 NETDATA.morrisInitialize(callback);
4839 NETDATA.chartLibraries.morris.enabled = false;
4840 if(typeof callback === "function")
4845 NETDATA._loadCSS(NETDATA.morris_css);
4848 url: NETDATA.morris_js,
4851 xhrFields: { withCredentials: true } // required for the cookie
4854 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4857 NETDATA.chartLibraries.morris.enabled = false;
4858 NETDATA.error(100, NETDATA.morris_js);
4860 .always(function() {
4861 if(typeof callback === "function")
4867 NETDATA.chartLibraries.morris.enabled = false;
4868 if(typeof callback === "function")
4873 NETDATA.morrisChartUpdate = function(state, data) {
4874 state.morris_instance.setData(data.result.data);
4878 NETDATA.morrisChartCreate = function(state, data) {
4880 state.morris_options = {
4881 element: state.element_chart.id,
4882 data: data.result.data,
4884 ykeys: data.dimension_names,
4885 labels: data.dimension_names,
4891 continuousLine: false,
4892 behaveLikeLine: false
4895 if(state.chart.chart_type === 'line')
4896 state.morris_instance = new Morris.Line(state.morris_options);
4898 else if(state.chart.chart_type === 'area') {
4899 state.morris_options.behaveLikeLine = true;
4900 state.morris_instance = new Morris.Area(state.morris_options);
4903 state.morris_instance = new Morris.Area(state.morris_options);
4908 // ----------------------------------------------------------------------------------------------------------------
4911 NETDATA.raphaelInitialize = function(callback) {
4912 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4914 url: NETDATA.raphael_js,
4917 xhrFields: { withCredentials: true } // required for the cookie
4920 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4923 NETDATA.chartLibraries.raphael.enabled = false;
4924 NETDATA.error(100, NETDATA.raphael_js);
4926 .always(function() {
4927 if(typeof callback === "function")
4932 NETDATA.chartLibraries.raphael.enabled = false;
4933 if(typeof callback === "function")
4938 NETDATA.raphaelChartUpdate = function(state, data) {
4939 $(state.element_chart).raphael(data.result, {
4940 width: state.chartWidth(),
4941 height: state.chartHeight()
4947 NETDATA.raphaelChartCreate = function(state, data) {
4948 $(state.element_chart).raphael(data.result, {
4949 width: state.chartWidth(),
4950 height: state.chartHeight()
4956 // ----------------------------------------------------------------------------------------------------------------
4959 NETDATA.c3Initialize = function(callback) {
4960 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4963 if(!NETDATA.chartLibraries.d3.initialized) {
4964 if(NETDATA.chartLibraries.d3.enabled) {
4965 NETDATA.d3Initialize(function() {
4966 NETDATA.c3Initialize(callback);
4970 NETDATA.chartLibraries.c3.enabled = false;
4971 if(typeof callback === "function")
4976 NETDATA._loadCSS(NETDATA.c3_css);
4982 xhrFields: { withCredentials: true } // required for the cookie
4985 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4988 NETDATA.chartLibraries.c3.enabled = false;
4989 NETDATA.error(100, NETDATA.c3_js);
4991 .always(function() {
4992 if(typeof callback === "function")
4998 NETDATA.chartLibraries.c3.enabled = false;
4999 if(typeof callback === "function")
5004 NETDATA.c3ChartUpdate = function(state, data) {
5005 state.c3_instance.destroy();
5006 return NETDATA.c3ChartCreate(state, data);
5008 //state.c3_instance.load({
5009 // rows: data.result,
5016 NETDATA.c3ChartCreate = function(state, data) {
5018 state.element_chart.id = 'c3-' + state.uuid;
5019 // console.log('id = ' + state.element_chart.id);
5021 state.c3_instance = c3.generate({
5022 bindto: '#' + state.element_chart.id,
5024 width: state.chartWidth(),
5025 height: state.chartHeight()
5028 pattern: state.chartColors()
5033 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5039 format: function(x) {
5040 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5067 // console.log(state.c3_instance);
5072 // ----------------------------------------------------------------------------------------------------------------
5075 NETDATA.d3Initialize = function(callback) {
5076 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5081 xhrFields: { withCredentials: true } // required for the cookie
5084 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5087 NETDATA.chartLibraries.d3.enabled = false;
5088 NETDATA.error(100, NETDATA.d3_js);
5090 .always(function() {
5091 if(typeof callback === "function")
5096 NETDATA.chartLibraries.d3.enabled = false;
5097 if(typeof callback === "function")
5102 NETDATA.d3ChartUpdate = function(state, data) {
5109 NETDATA.d3ChartCreate = function(state, data) {
5116 // ----------------------------------------------------------------------------------------------------------------
5119 NETDATA.googleInitialize = function(callback) {
5120 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5122 url: NETDATA.google_js,
5125 xhrFields: { withCredentials: true } // required for the cookie
5128 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5129 google.load('visualization', '1.1', {
5130 'packages': ['corechart', 'controls'],
5131 'callback': callback
5135 NETDATA.chartLibraries.google.enabled = false;
5136 NETDATA.error(100, NETDATA.google_js);
5137 if(typeof callback === "function")
5142 NETDATA.chartLibraries.google.enabled = false;
5143 if(typeof callback === "function")
5148 NETDATA.googleChartUpdate = function(state, data) {
5149 var datatable = new google.visualization.DataTable(data.result);
5150 state.google_instance.draw(datatable, state.google_options);
5154 NETDATA.googleChartCreate = function(state, data) {
5155 var datatable = new google.visualization.DataTable(data.result);
5157 state.google_options = {
5158 colors: state.chartColors(),
5160 // do not set width, height - the chart resizes itself
5161 //width: state.chartWidth(),
5162 //height: state.chartHeight(),
5167 // title: "Time of Day",
5168 // format:'HH:mm:ss',
5169 viewWindowMode: 'maximized',
5181 viewWindowMode: 'pretty',
5196 focusTarget: 'category',
5203 titlePosition: 'out',
5214 curveType: 'function',
5219 switch(state.chart.chart_type) {
5221 state.google_options.vAxis.viewWindowMode = 'maximized';
5222 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5223 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5227 state.google_options.isStacked = true;
5228 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5229 state.google_options.vAxis.viewWindowMode = 'maximized';
5230 state.google_options.vAxis.minValue = null;
5231 state.google_options.vAxis.maxValue = null;
5232 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5237 state.google_options.lineWidth = 2;
5238 state.google_instance = new google.visualization.LineChart(state.element_chart);
5242 state.google_instance.draw(datatable, state.google_options);
5246 // ----------------------------------------------------------------------------------------------------------------
5248 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5249 if(typeof value !== 'number') value = 0;
5250 if(typeof min !== 'number') min = 0;
5251 if(typeof max !== 'number') max = 0;
5253 if(min > value) min = value;
5254 if(max < value) max = value;
5256 // make sure it is zero based
5257 if(min > 0) min = 0;
5258 if(max < 0) max = 0;
5263 pcent = Math.round(value * 100 / max);
5264 if(pcent === 0) pcent = 0.1;
5268 pcent = Math.round(-value * 100 / min);
5269 if(pcent === 0) pcent = -0.1;
5275 // ----------------------------------------------------------------------------------------------------------------
5278 NETDATA.easypiechartInitialize = function(callback) {
5279 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5281 url: NETDATA.easypiechart_js,
5284 xhrFields: { withCredentials: true } // required for the cookie
5287 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5290 NETDATA.chartLibraries.easypiechart.enabled = false;
5291 NETDATA.error(100, NETDATA.easypiechart_js);
5293 .always(function() {
5294 if(typeof callback === "function")
5299 NETDATA.chartLibraries.easypiechart.enabled = false;
5300 if(typeof callback === "function")
5305 NETDATA.easypiechartClearSelection = function(state) {
5306 if(typeof state.easyPieChartEvent !== 'undefined') {
5307 if(state.easyPieChartEvent.timer !== undefined) {
5308 clearTimeout(state.easyPieChartEvent.timer);
5311 state.easyPieChartEvent.timer = undefined;
5314 if(state.isAutoRefreshable() === true && state.data !== null) {
5315 NETDATA.easypiechartChartUpdate(state, state.data);
5318 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5319 state.easyPieChart_instance.update(0);
5321 state.easyPieChart_instance.enableAnimation();
5326 NETDATA.easypiechartSetSelection = function(state, t) {
5327 if(state.timeIsVisible(t) !== true)
5328 return NETDATA.easypiechartClearSelection(state);
5330 var slot = state.calculateRowForTime(t);
5331 if(slot < 0 || slot >= state.data.result.length)
5332 return NETDATA.easypiechartClearSelection(state);
5334 if(typeof state.easyPieChartEvent === 'undefined') {
5335 state.easyPieChartEvent = {
5342 var value = state.data.result[state.data.result.length - 1 - slot];
5343 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5344 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5345 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5347 state.easyPieChartEvent.value = value;
5348 state.easyPieChartEvent.pcent = pcent;
5349 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5351 if(state.easyPieChartEvent.timer === undefined) {
5352 state.easyPieChart_instance.disableAnimation();
5354 state.easyPieChartEvent.timer = setTimeout(function() {
5355 state.easyPieChartEvent.timer = undefined;
5356 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5357 }, NETDATA.options.current.charts_selection_animation_delay);
5363 NETDATA.easypiechartChartUpdate = function(state, data) {
5364 var value, min, max, pcent;
5366 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5371 value = data.result[0];
5372 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5373 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5374 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5377 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5378 state.easyPieChart_instance.update(pcent);
5382 NETDATA.easypiechartChartCreate = function(state, data) {
5383 var self = $(state.element);
5384 var chart = $(state.element_chart);
5386 var value = data.result[0];
5387 var min = self.data('easypiechart-min-value') || null;
5388 var max = self.data('easypiechart-max-value') || null;
5389 var adjust = self.data('easypiechart-adjust') || null;
5392 min = NETDATA.commonMin.get(state);
5393 state.easyPieChartMin = null;
5396 state.easyPieChartMin = min;
5399 max = NETDATA.commonMax.get(state);
5400 state.easyPieChartMax = null;
5403 state.easyPieChartMax = max;
5405 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5407 chart.data('data-percent', pcent);
5411 case 'width': size = state.chartHeight(); break;
5412 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5413 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5415 default: size = state.chartWidth(); break;
5417 state.element.style.width = size + 'px';
5418 state.element.style.height = size + 'px';
5420 var stroke = Math.floor(size / 22);
5421 if(stroke < 3) stroke = 2;
5423 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5424 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5425 state.easyPieChartLabel = document.createElement('span');
5426 state.easyPieChartLabel.className = 'easyPieChartLabel';
5427 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5428 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5429 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5430 state.element_chart.appendChild(state.easyPieChartLabel);
5432 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5433 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5434 state.easyPieChartTitle = document.createElement('span');
5435 state.easyPieChartTitle.className = 'easyPieChartTitle';
5436 state.easyPieChartTitle.innerText = state.title;
5437 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5438 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5439 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5440 state.element_chart.appendChild(state.easyPieChartTitle);
5442 var unitfontsize = Math.round(titlefontsize * 0.9);
5443 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5444 state.easyPieChartUnits = document.createElement('span');
5445 state.easyPieChartUnits.className = 'easyPieChartUnits';
5446 state.easyPieChartUnits.innerText = state.units;
5447 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5448 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5449 state.element_chart.appendChild(state.easyPieChartUnits);
5451 var barColor = self.data('easypiechart-barcolor');
5452 if(typeof barColor === 'undefined' || barColor === null)
5453 barColor = state.chartColors()[0];
5455 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5456 var tmp = eval(barColor);
5457 if(typeof tmp === 'function')
5461 chart.easyPieChart({
5463 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5464 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5465 scaleLength: self.data('easypiechart-scalelength') || 5,
5466 lineCap: self.data('easypiechart-linecap') || 'round',
5467 lineWidth: self.data('easypiechart-linewidth') || stroke,
5468 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5469 size: self.data('easypiechart-size') || size,
5470 rotate: self.data('easypiechart-rotate') || 0,
5471 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5472 easing: self.data('easypiechart-easing') || undefined
5475 // when we just re-create the chart
5476 // do not animate the first update
5478 if(typeof state.easyPieChart_instance !== 'undefined')
5481 state.easyPieChart_instance = chart.data('easyPieChart');
5482 if(animate === false) state.easyPieChart_instance.disableAnimation();
5483 state.easyPieChart_instance.update(pcent);
5484 if(animate === false) state.easyPieChart_instance.enableAnimation();
5488 // ----------------------------------------------------------------------------------------------------------------
5491 NETDATA.gaugeInitialize = function(callback) {
5492 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5494 url: NETDATA.gauge_js,
5497 xhrFields: { withCredentials: true } // required for the cookie
5500 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5503 NETDATA.chartLibraries.gauge.enabled = false;
5504 NETDATA.error(100, NETDATA.gauge_js);
5506 .always(function() {
5507 if(typeof callback === "function")
5512 NETDATA.chartLibraries.gauge.enabled = false;
5513 if(typeof callback === "function")
5518 NETDATA.gaugeAnimation = function(state, status) {
5521 if(typeof status === 'boolean' && status === false)
5523 else if(typeof status === 'number')
5526 // console.log('gauge speed ' + speed);
5527 state.gauge_instance.animationSpeed = speed;
5528 state.___gaugeOld__.speed = speed;
5531 NETDATA.gaugeSet = function(state, value, min, max) {
5532 if(typeof value !== 'number') value = 0;
5533 if(typeof min !== 'number') min = 0;
5534 if(typeof max !== 'number') max = 0;
5535 if(value > max) max = value;
5536 if(value < min) min = value;
5542 else if(min === max)
5545 // gauge.js has an issue if the needle
5546 // is smaller than min or larger than max
5547 // when we set the new values
5548 // the needle will go crazy
5550 // to prevent it, we always feed it
5551 // with a percentage, so that the needle
5552 // is always between min and max
5553 var pcent = (value - min) * 100 / (max - min);
5555 // bug fix for gauge.js 1.3.1
5556 // if the value is the absolute min or max, the chart is broken
5557 if(pcent < 0.001) pcent = 0.001;
5558 if(pcent > 99.999) pcent = 99.999;
5560 state.gauge_instance.set(pcent);
5561 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5563 state.___gaugeOld__.value = value;
5564 state.___gaugeOld__.min = min;
5565 state.___gaugeOld__.max = max;
5568 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5569 if(state.___gaugeOld__.valueLabel !== value) {
5570 state.___gaugeOld__.valueLabel = value;
5571 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5573 if(state.___gaugeOld__.minLabel !== min) {
5574 state.___gaugeOld__.minLabel = min;
5575 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5577 if(state.___gaugeOld__.maxLabel !== max) {
5578 state.___gaugeOld__.maxLabel = max;
5579 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5583 NETDATA.gaugeClearSelection = function(state) {
5584 if(typeof state.gaugeEvent !== 'undefined') {
5585 if(state.gaugeEvent.timer !== undefined) {
5586 clearTimeout(state.gaugeEvent.timer);
5589 state.gaugeEvent.timer = undefined;
5592 if(state.isAutoRefreshable() === true && state.data !== null) {
5593 NETDATA.gaugeChartUpdate(state, state.data);
5596 NETDATA.gaugeAnimation(state, false);
5597 NETDATA.gaugeSet(state, null, null, null);
5598 NETDATA.gaugeSetLabels(state, null, null, null);
5601 NETDATA.gaugeAnimation(state, true);
5605 NETDATA.gaugeSetSelection = function(state, t) {
5606 if(state.timeIsVisible(t) !== true)
5607 return NETDATA.gaugeClearSelection(state);
5609 var slot = state.calculateRowForTime(t);
5610 if(slot < 0 || slot >= state.data.result.length)
5611 return NETDATA.gaugeClearSelection(state);
5613 if(typeof state.gaugeEvent === 'undefined') {
5614 state.gaugeEvent = {
5622 var value = state.data.result[state.data.result.length - 1 - slot];
5623 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5624 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5626 // make sure it is zero based
5627 if(min > 0) min = 0;
5628 if(max < 0) max = 0;
5630 state.gaugeEvent.value = value;
5631 state.gaugeEvent.min = min;
5632 state.gaugeEvent.max = max;
5633 NETDATA.gaugeSetLabels(state, value, min, max);
5635 if(state.gaugeEvent.timer === undefined) {
5636 NETDATA.gaugeAnimation(state, false);
5638 state.gaugeEvent.timer = setTimeout(function() {
5639 state.gaugeEvent.timer = undefined;
5640 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5641 }, NETDATA.options.current.charts_selection_animation_delay);
5647 NETDATA.gaugeChartUpdate = function(state, data) {
5648 var value, min, max;
5650 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5654 NETDATA.gaugeSetLabels(state, null, null, null);
5657 value = data.result[0];
5658 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5659 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5660 if(value < min) min = value;
5661 if(value > max) max = value;
5663 // make sure it is zero based
5664 if(min > 0) min = 0;
5665 if(max < 0) max = 0;
5667 NETDATA.gaugeSetLabels(state, value, min, max);
5670 NETDATA.gaugeSet(state, value, min, max);
5674 NETDATA.gaugeChartCreate = function(state, data) {
5675 var self = $(state.element);
5676 // var chart = $(state.element_chart);
5678 var value = data.result[0];
5679 var min = self.data('gauge-min-value') || null;
5680 var max = self.data('gauge-max-value') || null;
5681 var adjust = self.data('gauge-adjust') || null;
5682 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5683 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5684 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5685 var stopColor = self.data('gauge-stop-color') || void 0;
5686 var generateGradient = self.data('gauge-generate-gradient') || false;
5689 min = NETDATA.commonMin.get(state);
5690 state.gaugeMin = null;
5693 state.gaugeMin = min;
5696 max = NETDATA.commonMax.get(state);
5697 state.gaugeMax = null;
5700 state.gaugeMax = max;
5702 // make sure it is zero based
5703 if(min > 0) min = 0;
5704 if(max < 0) max = 0;
5706 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5708 // case 'width': width = height * ratio; break;
5710 // default: height = width / ratio; break;
5712 //state.element.style.width = width.toString() + 'px';
5713 //state.element.style.height = height.toString() + 'px';
5718 lines: 12, // The number of lines to draw
5719 angle: 0.15, // The span of the gauge arc
5720 lineWidth: 0.50, // The line thickness
5721 radiusScale: 0.85, // Relative radius
5723 length: 0.8, // 0.9 The radius of the inner circle
5724 strokeWidth: 0.035, // The rotation offset
5725 color: pointerColor // Fill color
5727 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5728 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5729 colorStart: startColor, // Colors
5730 colorStop: stopColor, // just experiment with them
5731 strokeColor: strokeColor, // to see which ones work best for you
5732 generateGradient: (generateGradient === true),
5734 highDpiSupport: true // High resolution support
5737 if (generateGradient.constructor === Array) {
5739 // data-gauge-generate-gradient="[0, 50, 100]"
5740 // data-gauge-gradient-percent-color-0="#FFFFFF"
5741 // data-gauge-gradient-percent-color-50="#999900"
5742 // data-gauge-gradient-percent-color-100="#000000"
5744 options.percentColors = [];
5745 var len = generateGradient.length;
5747 var pcent = generateGradient[len];
5748 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5749 if(color !== false) {
5753 options.percentColors.unshift(a);
5756 if(options.percentColors.length === 0)
5757 delete options.percentColors;
5759 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5760 //noinspection PointlessArithmeticExpressionJS
5761 options.percentColors = [
5762 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5763 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5764 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5765 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5766 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5767 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5768 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5769 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5770 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5771 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5772 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5775 state.gauge_canvas = document.createElement('canvas');
5776 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5777 state.gauge_canvas.className = 'gaugeChart';
5778 state.gauge_canvas.width = width;
5779 state.gauge_canvas.height = height;
5780 state.element_chart.appendChild(state.gauge_canvas);
5782 var valuefontsize = Math.floor(height / 6);
5783 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5784 state.gaugeChartLabel = document.createElement('span');
5785 state.gaugeChartLabel.className = 'gaugeChartLabel';
5786 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5787 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5788 state.element_chart.appendChild(state.gaugeChartLabel);
5790 var titlefontsize = Math.round(valuefontsize / 2);
5792 state.gaugeChartTitle = document.createElement('span');
5793 state.gaugeChartTitle.className = 'gaugeChartTitle';
5794 state.gaugeChartTitle.innerText = state.title;
5795 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5796 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5797 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5798 state.element_chart.appendChild(state.gaugeChartTitle);
5800 var unitfontsize = Math.round(titlefontsize * 0.9);
5801 state.gaugeChartUnits = document.createElement('span');
5802 state.gaugeChartUnits.className = 'gaugeChartUnits';
5803 state.gaugeChartUnits.innerText = state.units;
5804 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5805 state.element_chart.appendChild(state.gaugeChartUnits);
5807 state.gaugeChartMin = document.createElement('span');
5808 state.gaugeChartMin.className = 'gaugeChartMin';
5809 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5810 state.element_chart.appendChild(state.gaugeChartMin);
5812 state.gaugeChartMax = document.createElement('span');
5813 state.gaugeChartMax.className = 'gaugeChartMax';
5814 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5815 state.element_chart.appendChild(state.gaugeChartMax);
5817 // when we just re-create the chart
5818 // do not animate the first update
5820 if(typeof state.gauge_instance !== 'undefined')
5823 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5825 state.___gaugeOld__ = {
5834 // we will always feed a percentage
5835 state.gauge_instance.minValue = 0;
5836 state.gauge_instance.maxValue = 100;
5838 NETDATA.gaugeAnimation(state, animate);
5839 NETDATA.gaugeSet(state, value, min, max);
5840 NETDATA.gaugeSetLabels(state, value, min, max);
5841 NETDATA.gaugeAnimation(state, true);
5845 // ----------------------------------------------------------------------------------------------------------------
5846 // Charts Libraries Registration
5848 NETDATA.chartLibraries = {
5850 initialize: NETDATA.dygraphInitialize,
5851 create: NETDATA.dygraphChartCreate,
5852 update: NETDATA.dygraphChartUpdate,
5853 resize: function(state) {
5854 if(typeof state.dygraph_instance.resize === 'function')
5855 state.dygraph_instance.resize();
5857 setSelection: NETDATA.dygraphSetSelection,
5858 clearSelection: NETDATA.dygraphClearSelection,
5859 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5862 format: function(state) { void(state); return 'json'; },
5863 options: function(state) { void(state); return 'ms|flip'; },
5864 legend: function(state) {
5865 return (this.isSparkline(state) === false)?'right-side':null;
5867 autoresize: function(state) { void(state); return true; },
5868 max_updates_to_recreate: function(state) { void(state); return 5000; },
5869 track_colors: function(state) { void(state); return true; },
5870 pixels_per_point: function(state) {
5871 return (this.isSparkline(state) === false)?3:2;
5873 isSparkline: function(state) {
5874 if(typeof state.dygraph_sparkline === 'undefined') {
5875 var t = $(state.element).data('dygraph-theme');
5876 state.dygraph_sparkline = (t === 'sparkline');
5878 return state.dygraph_sparkline;
5882 initialize: NETDATA.sparklineInitialize,
5883 create: NETDATA.sparklineChartCreate,
5884 update: NETDATA.sparklineChartUpdate,
5886 setSelection: undefined, // function(state, t) { void(state); return true; },
5887 clearSelection: undefined, // function(state) { void(state); return true; },
5888 toolboxPanAndZoom: null,
5891 format: function(state) { void(state); return 'array'; },
5892 options: function(state) { void(state); return 'flip|abs'; },
5893 legend: function(state) { void(state); return null; },
5894 autoresize: function(state) { void(state); return false; },
5895 max_updates_to_recreate: function(state) { void(state); return 5000; },
5896 track_colors: function(state) { void(state); return false; },
5897 pixels_per_point: function(state) { void(state); return 3; }
5900 initialize: NETDATA.peityInitialize,
5901 create: NETDATA.peityChartCreate,
5902 update: NETDATA.peityChartUpdate,
5904 setSelection: undefined, // function(state, t) { void(state); return true; },
5905 clearSelection: undefined, // function(state) { void(state); return true; },
5906 toolboxPanAndZoom: null,
5909 format: function(state) { void(state); return 'ssvcomma'; },
5910 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5911 legend: function(state) { void(state); return null; },
5912 autoresize: function(state) { void(state); return false; },
5913 max_updates_to_recreate: function(state) { void(state); return 5000; },
5914 track_colors: function(state) { void(state); return false; },
5915 pixels_per_point: function(state) { void(state); return 3; }
5918 initialize: NETDATA.morrisInitialize,
5919 create: NETDATA.morrisChartCreate,
5920 update: NETDATA.morrisChartUpdate,
5922 setSelection: undefined, // function(state, t) { void(state); return true; },
5923 clearSelection: undefined, // function(state) { void(state); return true; },
5924 toolboxPanAndZoom: null,
5927 format: function(state) { void(state); return 'json'; },
5928 options: function(state) { void(state); return 'objectrows|ms'; },
5929 legend: function(state) { void(state); return null; },
5930 autoresize: function(state) { void(state); return false; },
5931 max_updates_to_recreate: function(state) { void(state); return 50; },
5932 track_colors: function(state) { void(state); return false; },
5933 pixels_per_point: function(state) { void(state); return 15; }
5936 initialize: NETDATA.googleInitialize,
5937 create: NETDATA.googleChartCreate,
5938 update: NETDATA.googleChartUpdate,
5940 setSelection: undefined, //function(state, t) { void(state); return true; },
5941 clearSelection: undefined, //function(state) { void(state); return true; },
5942 toolboxPanAndZoom: null,
5945 format: function(state) { void(state); return 'datatable'; },
5946 options: function(state) { void(state); return ''; },
5947 legend: function(state) { void(state); return null; },
5948 autoresize: function(state) { void(state); return false; },
5949 max_updates_to_recreate: function(state) { void(state); return 300; },
5950 track_colors: function(state) { void(state); return false; },
5951 pixels_per_point: function(state) { void(state); return 4; }
5954 initialize: NETDATA.raphaelInitialize,
5955 create: NETDATA.raphaelChartCreate,
5956 update: NETDATA.raphaelChartUpdate,
5958 setSelection: undefined, // function(state, t) { void(state); return true; },
5959 clearSelection: undefined, // function(state) { void(state); return true; },
5960 toolboxPanAndZoom: null,
5963 format: function(state) { void(state); return 'json'; },
5964 options: function(state) { void(state); return ''; },
5965 legend: function(state) { void(state); return null; },
5966 autoresize: function(state) { void(state); return false; },
5967 max_updates_to_recreate: function(state) { void(state); return 5000; },
5968 track_colors: function(state) { void(state); return false; },
5969 pixels_per_point: function(state) { void(state); return 3; }
5972 initialize: NETDATA.c3Initialize,
5973 create: NETDATA.c3ChartCreate,
5974 update: NETDATA.c3ChartUpdate,
5976 setSelection: undefined, // function(state, t) { void(state); return true; },
5977 clearSelection: undefined, // function(state) { void(state); return true; },
5978 toolboxPanAndZoom: null,
5981 format: function(state) { void(state); return 'csvjsonarray'; },
5982 options: function(state) { void(state); return 'milliseconds'; },
5983 legend: function(state) { void(state); return null; },
5984 autoresize: function(state) { void(state); return false; },
5985 max_updates_to_recreate: function(state) { void(state); return 5000; },
5986 track_colors: function(state) { void(state); return false; },
5987 pixels_per_point: function(state) { void(state); return 15; }
5990 initialize: NETDATA.d3Initialize,
5991 create: NETDATA.d3ChartCreate,
5992 update: NETDATA.d3ChartUpdate,
5994 setSelection: undefined, // function(state, t) { void(state); return true; },
5995 clearSelection: undefined, // function(state) { void(state); return true; },
5996 toolboxPanAndZoom: null,
5999 format: function(state) { void(state); return 'json'; },
6000 options: function(state) { void(state); return ''; },
6001 legend: function(state) { void(state); return null; },
6002 autoresize: function(state) { void(state); return false; },
6003 max_updates_to_recreate: function(state) { void(state); return 5000; },
6004 track_colors: function(state) { void(state); return false; },
6005 pixels_per_point: function(state) { void(state); return 3; }
6008 initialize: NETDATA.easypiechartInitialize,
6009 create: NETDATA.easypiechartChartCreate,
6010 update: NETDATA.easypiechartChartUpdate,
6012 setSelection: NETDATA.easypiechartSetSelection,
6013 clearSelection: NETDATA.easypiechartClearSelection,
6014 toolboxPanAndZoom: null,
6017 format: function(state) { void(state); return 'array'; },
6018 options: function(state) { void(state); return 'absolute'; },
6019 legend: function(state) { void(state); return null; },
6020 autoresize: function(state) { void(state); return false; },
6021 max_updates_to_recreate: function(state) { void(state); return 5000; },
6022 track_colors: function(state) { void(state); return true; },
6023 pixels_per_point: function(state) { void(state); return 3; },
6027 initialize: NETDATA.gaugeInitialize,
6028 create: NETDATA.gaugeChartCreate,
6029 update: NETDATA.gaugeChartUpdate,
6031 setSelection: NETDATA.gaugeSetSelection,
6032 clearSelection: NETDATA.gaugeClearSelection,
6033 toolboxPanAndZoom: null,
6036 format: function(state) { void(state); return 'array'; },
6037 options: function(state) { void(state); return 'absolute'; },
6038 legend: function(state) { void(state); return null; },
6039 autoresize: function(state) { void(state); return false; },
6040 max_updates_to_recreate: function(state) { void(state); return 5000; },
6041 track_colors: function(state) { void(state); return true; },
6042 pixels_per_point: function(state) { void(state); return 3; },
6047 NETDATA.registerChartLibrary = function(library, url) {
6048 if(NETDATA.options.debug.libraries === true)
6049 console.log("registering chart library: " + library);
6051 NETDATA.chartLibraries[library].url = url;
6052 NETDATA.chartLibraries[library].initialized = true;
6053 NETDATA.chartLibraries[library].enabled = true;
6056 // ----------------------------------------------------------------------------------------------------------------
6057 // Load required JS libraries and CSS
6059 NETDATA.requiredJs = [
6061 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6063 isAlreadyLoaded: function() {
6064 // check if bootstrap is loaded
6065 if(typeof $().emulateTransitionEnd === 'function')
6068 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6073 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6074 isAlreadyLoaded: function() { return false; }
6078 NETDATA.requiredCSS = [
6080 url: NETDATA.themes.current.bootstrap_css,
6081 isAlreadyLoaded: function() {
6082 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6086 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6087 isAlreadyLoaded: function() { return false; }
6090 url: NETDATA.themes.current.dashboard_css,
6091 isAlreadyLoaded: function() { return false; }
6095 NETDATA.loadedRequiredJs = 0;
6096 NETDATA.loadRequiredJs = function(index, callback) {
6097 if(index >= NETDATA.requiredJs.length) {
6098 if(typeof callback === 'function')
6103 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6104 NETDATA.loadedRequiredJs++;
6105 NETDATA.loadRequiredJs(++index, callback);
6109 if(NETDATA.options.debug.main_loop === true)
6110 console.log('loading ' + NETDATA.requiredJs[index].url);
6113 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6117 url: NETDATA.requiredJs[index].url,
6120 xhrFields: { withCredentials: true } // required for the cookie
6123 if(NETDATA.options.debug.main_loop === true)
6124 console.log('loaded ' + NETDATA.requiredJs[index].url);
6127 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6129 .always(function() {
6130 NETDATA.loadedRequiredJs++;
6133 NETDATA.loadRequiredJs(++index, callback);
6137 NETDATA.loadRequiredJs(++index, callback);
6140 NETDATA.loadRequiredCSS = function(index) {
6141 if(index >= NETDATA.requiredCSS.length)
6144 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6145 NETDATA.loadRequiredCSS(++index);
6149 if(NETDATA.options.debug.main_loop === true)
6150 console.log('loading ' + NETDATA.requiredCSS[index].url);
6152 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6153 NETDATA.loadRequiredCSS(++index);
6157 // ----------------------------------------------------------------------------------------------------------------
6158 // Registry of netdata hosts
6161 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6162 chart_div_offset: 100, // give that space above the chart when scrolling to it
6163 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6164 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6166 ms_penalty: 0, // the time penalty of the next alarm
6167 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6168 // if alarms are shown faster than: one per 500ms
6170 notifications: false, // when true, the browser supports notifications (may not be granted though)
6171 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6172 first_notification_id: 0, // the id of the first alarm_log entry for this session
6173 // this is used to prevent CLEAR notifications for past events
6174 // notifications_shown: [],
6176 server: null, // the server to connect to for fetching alarms
6177 current: null, // the list of raised alarms - updated in the background
6178 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6180 notify: function(entry) {
6181 // console.log('alarm ' + entry.unique_id);
6183 if(entry.updated === true) {
6184 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6188 var value_string = entry.value_string;
6190 if(NETDATA.alarms.current !== null) {
6191 // get the current value_string
6192 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6193 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6194 value_string = t.value_string;
6197 var name = entry.name.replace(/_/g, ' ');
6198 var status = entry.status.toLowerCase();
6199 var title = name + ' = ' + value_string.toString();
6200 var tag = entry.alarm_id;
6201 var icon = 'images/seo-performance-128.png';
6202 var interaction = false;
6206 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6208 switch(entry.status) {
6216 case 'UNINITIALIZED':
6220 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6221 // console.log('alarm ' + entry.unique_id + ' is not current');
6224 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6225 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6228 if(entry.no_clear_notification === true) {
6229 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6232 title = name + ' back to normal (' + value_string.toString() + ')';
6233 icon = 'images/check-mark-2-128-green.png';
6234 interaction = false;
6238 if(entry.old_status === 'CRITICAL')
6239 status = 'demoted to ' + entry.status.toLowerCase();
6241 icon = 'images/alert-128-orange.png';
6242 interaction = false;
6246 if(entry.old_status === 'WARNING')
6247 status = 'escalated to ' + entry.status.toLowerCase();
6249 icon = 'images/alert-128-red.png';
6254 console.log('invalid alarm status ' + entry.status);
6259 // cleanup old notifications with the same alarm_id as this one
6260 // FIXME: it does not seem to work on any web browser!
6261 var len = NETDATA.alarms.notifications_shown.length;
6263 var n = NETDATA.alarms.notifications_shown[len];
6264 if(n.data.alarm_id === entry.alarm_id) {
6265 console.log('removing old alarm ' + n.data.unique_id);
6267 // close the notification
6270 // remove it from the array
6271 NETDATA.alarms.notifications_shown.splice(len, 1);
6272 len = NETDATA.alarms.notifications_shown.length;
6279 setTimeout(function() {
6280 // show this notification
6281 // console.log('new notification: ' + title);
6282 var n = new Notification(title, {
6283 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6285 requireInteraction: interaction,
6286 icon: NETDATA.serverDefault + icon,
6290 n.onclick = function(event) {
6291 event.preventDefault();
6292 NETDATA.alarms.onclick(event.target.data);
6296 // NETDATA.alarms.notifications_shown.push(n);
6297 // console.log(entry);
6298 }, NETDATA.alarms.ms_penalty);
6300 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6304 scrollToChart: function(chart_id) {
6305 if(typeof chart_id === 'string') {
6306 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6307 if(typeof offset !== 'undefined') {
6308 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6315 scrollToAlarm: function(alarm) {
6316 if(typeof alarm === 'object') {
6317 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6319 if(ret === true && NETDATA.options.page_is_visible === false)
6321 // 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.');
6326 notifyAll: function() {
6327 // console.log('FETCHING ALARM LOG');
6328 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6329 // console.log('ALARM LOG FETCHED');
6331 if(data === null || typeof data !== 'object') {
6332 console.log('invalid alarms log response');
6336 if(data.length === 0) {
6337 console.log('received empty alarm log');
6341 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6343 data.sort(function(a, b) {
6344 if(a.unique_id > b.unique_id) return -1;
6345 if(a.unique_id < b.unique_id) return 1;
6349 NETDATA.alarms.ms_penalty = 0;
6351 var len = data.length;
6353 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6354 NETDATA.alarms.notify(data[len]);
6357 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6360 NETDATA.alarms.last_notification_id = data[0].unique_id;
6361 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6362 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6366 check_notifications: function() {
6367 // returns true if we should fire 1+ notifications
6369 if(NETDATA.alarms.notifications !== true) {
6370 // console.log('notifications not available');
6374 if(Notification.permission !== 'granted') {
6375 // console.log('notifications not granted');
6379 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6380 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6382 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6383 // console.log('new alarms detected');
6386 //else console.log('no new alarms');
6388 // else console.log('cannot process alarms');
6393 get: function(what, callback) {
6395 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6399 'Cache-Control': 'no-cache, no-store',
6400 'Pragma': 'no-cache'
6402 xhrFields: { withCredentials: true } // required for the cookie
6404 .done(function(data) {
6405 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6406 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6408 if(typeof callback === 'function')
6409 return callback(data);
6412 NETDATA.error(415, NETDATA.alarms.server);
6414 if(typeof callback === 'function')
6415 return callback(null);
6419 update_forever: function() {
6420 NETDATA.alarms.get('active', function(data) {
6422 NETDATA.alarms.current = data;
6424 if(NETDATA.alarms.check_notifications() === true) {
6425 NETDATA.alarms.notifyAll();
6428 if (typeof NETDATA.alarms.callback === 'function') {
6429 NETDATA.alarms.callback(data);
6432 // Health monitoring is disabled on this netdata
6433 if(data.status === false) return;
6436 setTimeout(NETDATA.alarms.update_forever, 10000);
6440 get_log: function(last_id, callback) {
6441 // console.log('fetching all log after ' + last_id.toString());
6443 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6447 'Cache-Control': 'no-cache, no-store',
6448 'Pragma': 'no-cache'
6450 xhrFields: { withCredentials: true } // required for the cookie
6452 .done(function(data) {
6453 if(typeof callback === 'function')
6454 return callback(data);
6457 NETDATA.error(416, NETDATA.alarms.server);
6459 if(typeof callback === 'function')
6460 return callback(null);
6465 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6467 NETDATA.alarms.last_notification_id =
6468 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6470 if(NETDATA.alarms.onclick === null)
6471 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6473 if(netdataShowAlarms === true) {
6474 NETDATA.alarms.update_forever();
6476 if('Notification' in window) {
6477 // console.log('notifications available');
6478 NETDATA.alarms.notifications = true;
6480 if(Notification.permission === 'default')
6481 Notification.requestPermission();
6487 // ----------------------------------------------------------------------------------------------------------------
6488 // Registry of netdata hosts
6490 NETDATA.registry = {
6491 server: null, // the netdata registry server
6492 person_guid: null, // the unique ID of this browser / user
6493 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6494 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6495 machines: null, // the user's other URLs
6496 machines_array: null, // the user's other URLs in an array
6499 parsePersonUrls: function(person_urls) {
6500 // console.log(person_urls);
6501 NETDATA.registry.person_urls = person_urls;
6504 NETDATA.registry.machines = {};
6505 NETDATA.registry.machines_array = [];
6507 var apu = person_urls;
6510 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6511 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6517 accesses: apu[i][3],
6521 obj.alternate_urls.push(apu[i][1]);
6523 NETDATA.registry.machines[apu[i][0]] = obj;
6524 NETDATA.registry.machines_array.push(obj);
6527 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6529 var pu = NETDATA.registry.machines[apu[i][0]];
6530 if(pu.last_t < apu[i][2]) {
6532 pu.last_t = apu[i][2];
6533 pu.name = apu[i][4];
6535 pu.accesses += apu[i][3];
6536 pu.alternate_urls.push(apu[i][1]);
6541 if(typeof netdataRegistryCallback === 'function')
6542 netdataRegistryCallback(NETDATA.registry.machines_array);
6546 if(netdataRegistry !== true) return;
6548 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6550 NETDATA.registry.server = data.registry;
6551 NETDATA.registry.machine_guid = data.machine_guid;
6552 NETDATA.registry.hostname = data.hostname;
6554 NETDATA.registry.access(2, function (person_urls) {
6555 NETDATA.registry.parsePersonUrls(person_urls);
6562 hello: function(host, callback) {
6563 host = NETDATA.fixHost(host);
6565 // send HELLO to a netdata server:
6566 // 1. verifies the server is reachable
6567 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6569 url: host + '/api/v1/registry?action=hello',
6573 'Cache-Control': 'no-cache, no-store',
6574 'Pragma': 'no-cache'
6576 xhrFields: { withCredentials: true } // required for the cookie
6578 .done(function(data) {
6579 if(typeof data.status !== 'string' || data.status !== 'ok') {
6580 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6584 if(typeof callback === 'function')
6585 return callback(data);
6588 NETDATA.error(407, host);
6590 if(typeof callback === 'function')
6591 return callback(null);
6595 access: function(max_redirects, callback) {
6596 // send ACCESS to a netdata registry:
6597 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6598 // 2. it responds with a list of netdata servers we know
6599 // the registry identifies us using a cookie it sets the first time we access it
6600 // the registry may respond with a redirect URL to send us to another registry
6602 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),
6606 'Cache-Control': 'no-cache, no-store',
6607 'Pragma': 'no-cache'
6609 xhrFields: { withCredentials: true } // required for the cookie
6611 .done(function(data) {
6612 var redirect = null;
6613 if(typeof data.registry === 'string')
6614 redirect = data.registry;
6616 if(typeof data.status !== 'string' || data.status !== 'ok') {
6617 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6622 if(redirect !== null && max_redirects > 0) {
6623 NETDATA.registry.server = redirect;
6624 NETDATA.registry.access(max_redirects - 1, callback);
6627 if(typeof callback === 'function')
6628 return callback(null);
6632 if(typeof data.person_guid === 'string')
6633 NETDATA.registry.person_guid = data.person_guid;
6635 if(typeof callback === 'function')
6636 return callback(data.urls);
6640 NETDATA.error(410, NETDATA.registry.server);
6642 if(typeof callback === 'function')
6643 return callback(null);
6647 delete: function(delete_url, callback) {
6648 // send DELETE to a netdata registry:
6650 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),
6654 'Cache-Control': 'no-cache, no-store',
6655 'Pragma': 'no-cache'
6657 xhrFields: { withCredentials: true } // required for the cookie
6659 .done(function(data) {
6660 if(typeof data.status !== 'string' || data.status !== 'ok') {
6661 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6665 if(typeof callback === 'function')
6666 return callback(data);
6669 NETDATA.error(412, NETDATA.registry.server);
6671 if(typeof callback === 'function')
6672 return callback(null);
6676 search: function(machine_guid, callback) {
6677 // SEARCH for the URLs of a machine:
6679 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,
6683 'Cache-Control': 'no-cache, no-store',
6684 'Pragma': 'no-cache'
6686 xhrFields: { withCredentials: true } // required for the cookie
6688 .done(function(data) {
6689 if(typeof data.status !== 'string' || data.status !== 'ok') {
6690 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6694 if(typeof callback === 'function')
6695 return callback(data);
6698 NETDATA.error(418, NETDATA.registry.server);
6700 if(typeof callback === 'function')
6701 return callback(null);
6705 switch: function(new_person_guid, callback) {
6708 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,
6712 'Cache-Control': 'no-cache, no-store',
6713 'Pragma': 'no-cache'
6715 xhrFields: { withCredentials: true } // required for the cookie
6717 .done(function(data) {
6718 if(typeof data.status !== 'string' || data.status !== 'ok') {
6719 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6723 if(typeof callback === 'function')
6724 return callback(data);
6727 NETDATA.error(414, NETDATA.registry.server);
6729 if(typeof callback === 'function')
6730 return callback(null);
6735 // ----------------------------------------------------------------------------------------------------------------
6738 if(typeof netdataPrepCallback === 'function')
6739 netdataPrepCallback();
6741 NETDATA.errorReset();
6742 NETDATA.loadRequiredCSS(0);
6744 NETDATA._loadjQuery(function() {
6745 NETDATA.loadRequiredJs(0, function() {
6746 if(typeof $().emulateTransitionEnd !== 'function') {
6747 // bootstrap is not available
6748 NETDATA.options.current.show_help = false;
6751 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6752 if(NETDATA.options.debug.main_loop === true)
6753 console.log('starting chart refresh thread');
6759 })(window, document);