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.2.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 = d;
1372 force_update_at: 0, // the timestamp to force the update at
1373 force_before_ms: null,
1374 force_after_ms: null
1379 force_update_at: 0, // the timestamp to force the update at
1380 force_before_ms: null,
1381 force_after_ms: null
1386 force_update_at: 0, // the timestamp to force the update at
1387 force_before_ms: null,
1388 force_after_ms: null
1391 // this is a pointer to one of the sub-classes below
1393 this.current = this.auto;
1395 // check the requested library is available
1396 // we don't initialize it here - it will be initialized when
1397 // this chart will be first used
1398 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1399 NETDATA.error(402, that.library_name);
1400 error('chart library "' + that.library_name + '" is not found');
1403 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1404 NETDATA.error(403, that.library_name);
1405 error('chart library "' + that.library_name + '" is not enabled');
1409 that.library = NETDATA.chartLibraries[that.library_name];
1411 // milliseconds - the time the last refresh took
1412 this.refresh_dt_ms = 0;
1414 // if we need to report the rendering speed
1415 // find the element that needs to be updated
1416 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1418 if(refresh_dt_element_name !== null) {
1419 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1422 this.refresh_dt_element = null;
1424 this.dimensions_visibility = new dimensionsVisibility(this);
1426 this._updating = false;
1428 // ============================================================================================================
1429 // PRIVATE FUNCTIONS
1431 var createDOM = function() {
1432 if(that.enabled === false) return;
1434 if(that.element_message !== null) that.element_message.innerHTML = '';
1435 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1436 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1438 that.element.innerHTML = '';
1440 that.element_message = document.createElement('div');
1441 that.element_message.className = 'netdata-message icon hidden';
1442 that.element.appendChild(that.element_message);
1444 that.element_chart = document.createElement('div');
1445 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1446 that.element.appendChild(that.element_chart);
1448 if(that.hasLegend() === true) {
1449 that.element.className = "netdata-container-with-legend";
1450 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1452 that.element_legend = document.createElement('div');
1453 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1454 that.element.appendChild(that.element_legend);
1457 that.element.className = "netdata-container";
1458 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1460 that.element_legend = null;
1462 that.element_legend_childs.series = null;
1464 if(typeof(that.width) === 'string')
1465 $(that.element).css('width', that.width);
1466 else if(typeof(that.width) === 'number')
1467 $(that.element).css('width', that.width + 'px');
1469 if(typeof(that.library.aspect_ratio) === 'undefined') {
1470 if(typeof(that.height) === 'string')
1471 that.element.style.height = that.height;
1472 else if(typeof(that.height) === 'number')
1473 that.element.style.height = that.height.toString() + 'px';
1476 var w = that.element.offsetWidth;
1477 if(w === null || w === 0) {
1478 // the div is hidden
1479 // this will resize the chart when next viewed
1480 that.tm.last_resized = 0;
1483 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1486 if(NETDATA.chartDefaults.min_width !== null)
1487 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1489 that.tm.last_dom_created = Date.now();
1495 * initialize state variables
1496 * destroy all (possibly) created state elements
1497 * create the basic DOM for a chart
1499 var init = function() {
1500 if(that.enabled === false) return;
1502 that.paused = false;
1503 that.selected = false;
1505 that.chart_created = false; // boolean - is the library.create() been called?
1506 that.updates_counter = 0; // numeric - the number of refreshes made so far
1507 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1508 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1511 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1512 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1513 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1515 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1516 last_updated: 0, // the timestamp the chart last updated with data
1517 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1519 // Used with NETDATA.globalPanAndZoom.seq
1520 last_visible_check: 0, // the time we last checked if it is visible
1521 last_resized: 0, // the time the chart was resized
1522 last_hidden: 0, // the time the chart was hidden
1523 last_unhidden: 0, // the time the chart was unhidden
1524 last_autorefreshed: 0 // the time the chart was last refreshed
1527 that.data = null; // the last data as downloaded from the netdata server
1528 that.data_url = 'invalid://'; // string - the last url used to update the chart
1529 that.data_points = 0; // number - the number of points returned from netdata
1530 that.data_after = 0; // milliseconds - the first timestamp of the data
1531 that.data_before = 0; // milliseconds - the last timestamp of the data
1532 that.data_update_every = 0; // milliseconds - the frequency to update the data
1534 that.tm.last_initialized = Date.now();
1537 that.setMode('auto');
1540 var maxMessageFontSize = function() {
1541 var screenHeight = screen.height;
1542 var el = that.element;
1544 // normally we want a font size, as tall as the element
1545 var h = el.clientHeight;
1547 // but give it some air, 20% let's say, or 5 pixels min
1548 var lost = Math.max(h * 0.2, 5);
1551 // center the text, vertically
1552 var paddingTop = (lost - 5) / 2;
1554 // but check the width too
1555 // it should fit 10 characters in it
1556 var w = el.clientWidth / 10;
1558 paddingTop += (h - w) / 2;
1562 // and don't make it too huge
1563 // 5% of the screen size is good
1564 if(h > screenHeight / 20) {
1565 paddingTop += (h - (screenHeight / 20)) / 2;
1566 h = screenHeight / 20;
1570 that.element_message.style.fontSize = h.toString() + 'px';
1571 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1574 var showMessageIcon = function(icon) {
1575 that.element_message.innerHTML = icon;
1576 maxMessageFontSize();
1577 $(that.element_message).removeClass('hidden');
1578 that.___messageHidden___ = undefined;
1581 var hideMessage = function() {
1582 if(typeof that.___messageHidden___ === 'undefined') {
1583 that.___messageHidden___ = true;
1584 $(that.element_message).addClass('hidden');
1588 var showRendering = function() {
1590 if(that.chart !== null) {
1591 if(that.chart.chart_type === 'line')
1592 icon = '<i class="fa fa-line-chart"></i>';
1594 icon = '<i class="fa fa-area-chart"></i>';
1597 icon = '<i class="fa fa-area-chart"></i>';
1599 showMessageIcon(icon + ' netdata');
1602 var showLoading = function() {
1603 if(that.chart_created === false) {
1604 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1610 var isHidden = function() {
1611 return (typeof that.___chartIsHidden___ !== 'undefined');
1614 // hide the chart, when it is not visible - called from isVisible()
1615 var hideChart = function() {
1616 // hide it, if it is not already hidden
1617 if(isHidden() === true) return;
1619 if(that.chart_created === true) {
1620 if(NETDATA.options.current.destroy_on_hide === true) {
1621 // we should destroy it
1626 that.element_chart.style.display = 'none';
1627 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1628 that.tm.last_hidden = Date.now();
1631 // This works, but I not sure there are no corner cases somewhere
1632 // so it is commented - if the user has memory issues he can
1633 // set Destroy on Hide for all charts
1634 // that.data = null;
1638 that.___chartIsHidden___ = true;
1641 // unhide the chart, when it is visible - called from isVisible()
1642 var unhideChart = function() {
1643 if(isHidden() === false) return;
1645 that.___chartIsHidden___ = undefined;
1646 that.updates_since_last_unhide = 0;
1648 if(that.chart_created === false) {
1649 // we need to re-initialize it, to show our background
1650 // logo in bootstrap tabs, until the chart loads
1654 that.tm.last_unhidden = Date.now();
1655 that.element_chart.style.display = '';
1656 if(that.element_legend !== null) that.element_legend.style.display = '';
1662 var canBeRendered = function() {
1663 return (isHidden() === false && that.isVisible(true) === true);
1666 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1667 var callChartLibraryUpdateSafely = function(data) {
1670 if(canBeRendered() === false)
1673 if(NETDATA.options.debug.chart_errors === true)
1674 status = that.library.update(that, data);
1677 status = that.library.update(that, data);
1684 if(status === false) {
1685 error('chart failed to be updated as ' + that.library_name);
1692 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1693 var callChartLibraryCreateSafely = function(data) {
1696 if(canBeRendered() === false)
1699 if(NETDATA.options.debug.chart_errors === true)
1700 status = that.library.create(that, data);
1703 status = that.library.create(that, data);
1710 if(status === false) {
1711 error('chart failed to be created as ' + that.library_name);
1715 that.chart_created = true;
1716 that.updates_since_last_creation = 0;
1720 // ----------------------------------------------------------------------------------------------------------------
1723 // resizeChart() - private
1724 // to be called just before the chart library to make sure that
1725 // a properly sized dom is available
1726 var resizeChart = function() {
1727 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1728 if(that.chart_created === false) return;
1730 if(that.needsRecreation()) {
1733 else if(typeof that.library.resize === 'function') {
1734 that.library.resize(that);
1736 if(that.element_legend_childs.perfect_scroller !== null)
1737 Ps.update(that.element_legend_childs.perfect_scroller);
1739 maxMessageFontSize();
1742 that.tm.last_resized = Date.now();
1746 // this is the actual chart resize algorithm
1748 // - resize the entire container
1749 // - update the internal states
1750 // - resize the chart as the div changes height
1751 // - update the scrollbar of the legend
1752 var resizeChartToHeight = function(h) {
1754 that.element.style.height = h;
1756 if(that.settings_id !== null)
1757 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1759 var now = Date.now();
1760 NETDATA.options.last_page_scroll = now;
1761 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1764 that.tm.last_resized = 0;
1768 this.resizeHandler = function(e) {
1771 if(typeof this.event_resize === 'undefined'
1772 || this.event_resize.chart_original_w === 'undefined'
1773 || this.event_resize.chart_original_h === 'undefined')
1774 this.event_resize = {
1775 chart_original_w: this.element.clientWidth,
1776 chart_original_h: this.element.clientHeight,
1780 if(e.type === 'touchstart') {
1781 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1782 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1785 this.event_resize.mouse_start_x = e.clientX;
1786 this.event_resize.mouse_start_y = e.clientY;
1789 this.event_resize.chart_start_w = this.element.clientWidth;
1790 this.event_resize.chart_start_h = this.element.clientHeight;
1791 this.event_resize.chart_last_w = this.element.clientWidth;
1792 this.event_resize.chart_last_h = this.element.clientHeight;
1794 var now = Date.now();
1795 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1796 // double click / double tap event
1798 // console.dir(this.element_legend_childs.content);
1799 // console.dir(this.element_legend_childs.perfect_scroller);
1801 // the optimal height of the chart
1802 // showing the entire legend
1803 var optimal = this.event_resize.chart_last_h
1804 + this.element_legend_childs.perfect_scroller.scrollHeight
1805 - this.element_legend_childs.perfect_scroller.clientHeight;
1807 // if we are not optimal, be optimal
1808 if(this.event_resize.chart_last_h !== optimal) {
1809 // 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());
1810 resizeChartToHeight(optimal.toString() + 'px');
1813 // else if the current height is not the original/saved height
1814 // reset to the original/saved height
1815 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1816 // 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());
1817 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1820 // else if the current height is not the internal default height
1821 // reset to the internal default height
1822 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1823 // 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());
1824 resizeChartToHeight(this.height_original.toString());
1827 // else if the current height is not the firstchild's clientheight
1829 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1830 var parent_rect = this.element.getBoundingClientRect();
1831 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1832 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1834 // console.log(parent_rect);
1835 // console.log(content_rect);
1836 // console.log(wanted);
1838 // 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' );
1839 if(this.event_resize.chart_last_h !== wanted)
1840 resizeChartToHeight(wanted.toString() + 'px');
1844 this.event_resize.last = now;
1846 // process movement event
1847 document.onmousemove =
1848 document.ontouchmove =
1849 this.element_legend_childs.resize_handler.onmousemove =
1850 this.element_legend_childs.resize_handler.ontouchmove =
1855 case 'mousemove': y = e.clientY; break;
1856 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1860 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1862 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1863 resizeChartToHeight(newH.toString() + 'px');
1864 that.event_resize.chart_last_h = newH;
1869 // process end event
1870 document.onmouseup =
1871 document.ontouchend =
1872 this.element_legend_childs.resize_handler.onmouseup =
1873 this.element_legend_childs.resize_handler.ontouchend =
1877 // remove all the hooks
1878 document.onmouseup =
1879 document.onmousemove =
1880 document.ontouchmove =
1881 document.ontouchend =
1882 that.element_legend_childs.resize_handler.onmousemove =
1883 that.element_legend_childs.resize_handler.ontouchmove =
1884 that.element_legend_childs.resize_handler.onmouseout =
1885 that.element_legend_childs.resize_handler.onmouseup =
1886 that.element_legend_childs.resize_handler.ontouchend =
1889 // allow auto-refreshes
1890 NETDATA.options.auto_refresher_stop_until = 0;
1896 var noDataToShow = function() {
1897 showMessageIcon('<i class="fa fa-warning"></i> empty');
1898 that.legendUpdateDOM();
1899 that.tm.last_autorefreshed = Date.now();
1900 // that.data_update_every = 30 * 1000;
1901 //that.element_chart.style.display = 'none';
1902 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1903 //that.___chartIsHidden___ = true;
1906 // ============================================================================================================
1909 this.error = function(msg) {
1913 this.setMode = function(m) {
1914 if(this.current !== null && this.current.name === m) return;
1917 this.current = this.auto;
1918 else if(m === 'pan')
1919 this.current = this.pan;
1920 else if(m === 'zoom')
1921 this.current = this.zoom;
1923 this.current = this.auto;
1925 this.current.force_update_at = 0;
1926 this.current.force_before_ms = null;
1927 this.current.force_after_ms = null;
1929 this.tm.last_mode_switch = Date.now();
1932 // ----------------------------------------------------------------------------------------------------------------
1933 // global selection sync
1935 // prevent to global selection sync for some time
1936 this.globalSelectionSyncDelay = function(ms) {
1937 if(NETDATA.options.current.sync_selection === false)
1940 if(typeof ms === 'number')
1941 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1943 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1946 // can we globally apply selection sync?
1947 this.globalSelectionSyncAbility = function() {
1948 if(NETDATA.options.current.sync_selection === false)
1951 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1954 this.globalSelectionSyncIsMaster = function() {
1955 return (NETDATA.globalSelectionSync.state === this);
1958 // this chart is the master of the global selection sync
1959 this.globalSelectionSyncBeMaster = function() {
1961 if(this.globalSelectionSyncIsMaster()) {
1962 if(this.debug === true)
1963 this.log('sync: I am the master already.');
1968 if(NETDATA.globalSelectionSync.state) {
1969 if(this.debug === true)
1970 this.log('sync: I am not the sync master. Resetting global sync.');
1972 this.globalSelectionSyncStop();
1975 // become the master
1976 if(this.debug === true)
1977 this.log('sync: becoming sync master.');
1979 this.selected = true;
1980 NETDATA.globalSelectionSync.state = this;
1982 // find the all slaves
1983 var targets = NETDATA.options.targets;
1984 var len = targets.length;
1986 var st = targets[len];
1989 if(this.debug === true)
1990 st.log('sync: not adding me to sync');
1992 else if(st.globalSelectionSyncIsEligible()) {
1993 if(this.debug === true)
1994 st.log('sync: adding to sync as slave');
1996 st.globalSelectionSyncBeSlave();
2000 // this.globalSelectionSyncDelay(100);
2003 // can the chart participate to the global selection sync as a slave?
2004 this.globalSelectionSyncIsEligible = function() {
2005 return (this.enabled === true
2006 && this.library !== null
2007 && typeof this.library.setSelection === 'function'
2008 && this.isVisible() === true
2009 && this.chart_created === true);
2012 // this chart becomes a slave of the global selection sync
2013 this.globalSelectionSyncBeSlave = function() {
2014 if(NETDATA.globalSelectionSync.state !== this)
2015 NETDATA.globalSelectionSync.slaves.push(this);
2018 // sync all the visible charts to the given time
2019 // this is to be called from the chart libraries
2020 this.globalSelectionSync = function(t) {
2021 if(this.globalSelectionSyncAbility() === false)
2024 if(this.globalSelectionSyncIsMaster() === false) {
2025 if(this.debug === true)
2026 this.log('sync: trying to be sync master.');
2028 this.globalSelectionSyncBeMaster();
2030 if(this.globalSelectionSyncAbility() === false)
2034 NETDATA.globalSelectionSync.last_t = t;
2035 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2040 // stop syncing all charts to the given time
2041 this.globalSelectionSyncStop = function() {
2042 if(NETDATA.globalSelectionSync.slaves.length) {
2043 if(this.debug === true)
2044 this.log('sync: cleaning up...');
2046 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2048 if(that.debug === true)
2049 st.log('sync: not adding me to sync stop');
2052 if(that.debug === true)
2053 st.log('sync: removed slave from sync');
2055 st.clearSelection();
2059 NETDATA.globalSelectionSync.last_t = 0;
2060 NETDATA.globalSelectionSync.slaves = [];
2061 NETDATA.globalSelectionSync.state = null;
2064 this.clearSelection();
2067 this.setSelection = function(t) {
2068 if(typeof this.library.setSelection === 'function')
2069 this.selected = (this.library.setSelection(this, t) === true);
2071 this.selected = true;
2073 if(this.selected === true && this.debug === true)
2074 this.log('selection set to ' + t.toString());
2076 return this.selected;
2079 this.clearSelection = function() {
2080 if(this.selected === true) {
2081 if(typeof this.library.clearSelection === 'function')
2082 this.selected = (this.library.clearSelection(this) !== true);
2084 this.selected = false;
2086 if(this.selected === false && this.debug === true)
2087 this.log('selection cleared');
2092 return this.selected;
2095 // find if a timestamp (ms) is shown in the current chart
2096 this.timeIsVisible = function(t) {
2097 return (t >= this.data_after && t <= this.data_before);
2100 this.calculateRowForTime = function(t) {
2101 if(this.timeIsVisible(t) === false) return -1;
2102 return Math.floor((t - this.data_after) / this.data_update_every);
2105 // ----------------------------------------------------------------------------------------------------------------
2108 this.log = function(msg) {
2109 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2112 this.pauseChart = function() {
2113 if(this.paused === false) {
2114 if(this.debug === true)
2115 this.log('pauseChart()');
2121 this.unpauseChart = function() {
2122 if(this.paused === true) {
2123 if(this.debug === true)
2124 this.log('unpauseChart()');
2126 this.paused = false;
2130 this.resetChart = function(dont_clear_master, dont_update) {
2131 if(this.debug === true)
2132 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2134 if(typeof dont_clear_master === 'undefined')
2135 dont_clear_master = false;
2137 if(typeof dont_update === 'undefined')
2138 dont_update = false;
2140 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2141 if(this.debug === true)
2142 this.log('resetChart() diverting to clearMaster().');
2143 // this will call us back with master === true
2144 NETDATA.globalPanAndZoom.clearMaster();
2148 this.clearSelection();
2150 this.tm.pan_and_zoom_seq = 0;
2152 this.setMode('auto');
2153 this.current.force_update_at = 0;
2154 this.current.force_before_ms = null;
2155 this.current.force_after_ms = null;
2156 this.tm.last_autorefreshed = 0;
2157 this.paused = false;
2158 this.selected = false;
2159 this.enabled = true;
2160 // this.debug = false;
2162 // do not update the chart here
2163 // or the chart will flip-flop when it is the master
2164 // of a selection sync and another chart becomes
2167 if(dont_update !== true && this.isVisible() === true) {
2172 this.updateChartPanOrZoom = function(after, before) {
2173 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2176 if(this.debug === true)
2179 if(before < after) {
2180 if(this.debug === true)
2181 this.log(logme + 'flipped parameters, rejecting it.');
2186 if(typeof this.fixed_min_duration === 'undefined')
2187 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2189 var min_duration = this.fixed_min_duration;
2190 var current_duration = Math.round(this.view_before - this.view_after);
2192 // round the numbers
2193 after = Math.round(after);
2194 before = Math.round(before);
2196 // align them to update_every
2197 // stretching them further away
2198 after -= after % this.data_update_every;
2199 before += this.data_update_every - (before % this.data_update_every);
2201 // the final wanted duration
2202 var wanted_duration = before - after;
2204 // to allow panning, accept just a point below our minimum
2205 if((current_duration - this.data_update_every) < min_duration)
2206 min_duration = current_duration - this.data_update_every;
2208 // we do it, but we adjust to minimum size and return false
2209 // when the wanted size is below the current and the minimum
2211 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2212 if(this.debug === true)
2213 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2215 min_duration = this.fixed_min_duration;
2217 var dt = (min_duration - wanted_duration) / 2;
2220 wanted_duration = before - after;
2224 var tolerance = this.data_update_every * 2;
2225 var movement = Math.abs(before - this.view_before);
2227 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2228 if(this.debug === true)
2229 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);
2233 if(this.current.name === 'auto') {
2234 this.log(logme + 'caller called me with mode: ' + this.current.name);
2235 this.setMode('pan');
2238 if(this.debug === true)
2239 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);
2241 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2242 this.current.force_after_ms = after;
2243 this.current.force_before_ms = before;
2244 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2248 var __legendFormatValueChartDecimalsLastMin = undefined;
2249 var __legendFormatValueChartDecimalsLastMax = undefined;
2250 var __legendFormatValueChartDecimals = -1;
2251 this.legendFormatValueDecimalsFromMinMax = function(min, max) {
2252 if(min === __legendFormatValueChartDecimalsLastMin && max === __legendFormatValueChartDecimalsLastMax)
2255 __legendFormatValueChartDecimalsLastMin = min;
2256 __legendFormatValueChartDecimalsLastMax = max;
2258 if(this.data !== null && this.data.min === this.data.max)
2259 __legendFormatValueChartDecimals = -1;
2261 else if(this.value_decimal_detail !== -1)
2262 __legendFormatValueChartDecimals = this.value_decimal_detail;
2268 delta = Math.abs(min);
2270 delta = Math.abs(max - min);
2272 if (delta > 1000) __legendFormatValueChartDecimals = 0;
2273 else if (delta > 10) __legendFormatValueChartDecimals = 1;
2274 else if (delta > 1) __legendFormatValueChartDecimals = 2;
2275 else if (delta > 0.1) __legendFormatValueChartDecimals = 2;
2276 else __legendFormatValueChartDecimals = 4;
2280 this.legendFormatValue = function(value) {
2281 if(typeof value !== 'number') return '-';
2285 if(__legendFormatValueChartDecimals < 0) {
2288 if(abs > 1000) dmax = 0;
2289 else if(abs > 10 ) dmax = 1;
2290 else if(abs > 1) dmax = 2;
2291 else if(abs > 0.1) dmax = 2;
2295 dmin = dmax = __legendFormatValueChartDecimals;
2298 if(this.value_decimal_detail !== -1) {
2299 dmin = dmax = this.value_decimal_detail;
2302 return value.toLocaleString(undefined, {
2303 // style: 'decimal',
2304 // minimumIntegerDigits: 1,
2305 // minimumSignificantDigits: 1,
2306 // maximumSignificantDigits: 1,
2308 minimumFractionDigits: dmin,
2309 maximumFractionDigits: dmax
2313 this.legendSetLabelValue = function(label, value) {
2314 var series = this.element_legend_childs.series[label];
2315 if(typeof series === 'undefined') return;
2316 if(series.value === null && series.user === null) return;
2319 // this slows down firefox and edge significantly
2320 // since it requires to use innerHTML(), instead of innerText()
2322 // if the value has not changed, skip DOM update
2323 //if(series.last === value) return;
2326 if(typeof value === 'number') {
2327 var v = Math.abs(value);
2328 s = r = this.legendFormatValue(value);
2330 if(typeof series.last === 'number') {
2331 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2332 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2333 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2335 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2345 series.last = value;
2349 var s = this.legendFormatValue(value);
2351 // caching: do not update the update to show the same value again
2352 if(s === series.last_shown_value) return;
2353 series.last_shown_value = s;
2355 if(series.value !== null) series.value.innerText = s;
2356 if(series.user !== null) series.user.innerText = s;
2359 this.__legendSetDateString = function(date) {
2360 if(date !== this.__last_shown_legend_date) {
2361 this.element_legend_childs.title_date.innerText = date;
2362 this.__last_shown_legend_date = date;
2366 this.__legendSetTimeString = function(time) {
2367 if(time !== this.__last_shown_legend_time) {
2368 this.element_legend_childs.title_time.innerText = time;
2369 this.__last_shown_legend_time = time;
2373 this.__legendSetUnitsString = function(units) {
2374 if(units !== this.__last_shown_legend_units) {
2375 this.element_legend_childs.title_units.innerText = units;
2376 this.__last_shown_legend_units = units;
2380 this.legendSetDateLast = {
2386 this.legendSetDate = function(ms) {
2387 if(typeof ms !== 'number') {
2388 this.legendShowUndefined();
2392 if(this.legendSetDateLast.ms !== ms) {
2393 var d = new Date(ms);
2394 this.legendSetDateLast.ms = ms;
2395 this.legendSetDateLast.date = d.toLocaleDateString();
2396 this.legendSetDateLast.time = d.toLocaleTimeString();
2399 if(this.element_legend_childs.title_date !== null)
2400 this.__legendSetDateString(this.legendSetDateLast.date);
2402 if(this.element_legend_childs.title_time !== null)
2403 this.__legendSetTimeString(this.legendSetDateLast.time);
2405 if(this.element_legend_childs.title_units !== null)
2406 this.__legendSetUnitsString(this.units)
2409 this.legendShowUndefined = function() {
2410 if(this.element_legend_childs.title_date !== null)
2411 this.__legendSetDateString(' ');
2413 if(this.element_legend_childs.title_time !== null)
2414 this.__legendSetTimeString(this.chart.name);
2416 if(this.element_legend_childs.title_units !== null)
2417 this.__legendSetUnitsString(' ');
2419 if(this.data && this.element_legend_childs.series !== null) {
2420 var labels = this.data.dimension_names;
2421 var i = labels.length;
2423 var label = labels[i];
2425 if(typeof label === 'undefined' || typeof this.element_legend_childs.series[label] === 'undefined') continue;
2426 this.legendSetLabelValue(label, null);
2431 this.legendShowLatestValues = function() {
2432 if(this.chart === null) return;
2433 if(this.selected) return;
2435 if(this.data === null || this.element_legend_childs.series === null) {
2436 this.legendShowUndefined();
2440 var show_undefined = true;
2441 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2442 show_undefined = false;
2444 if(show_undefined) {
2445 this.legendShowUndefined();
2449 this.legendSetDate(this.view_before);
2451 var labels = this.data.dimension_names;
2452 var i = labels.length;
2454 var label = labels[i];
2456 if(typeof label === 'undefined') continue;
2457 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2460 this.legendSetLabelValue(label, null);
2462 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2466 this.legendReset = function() {
2467 this.legendShowLatestValues();
2470 // this should be called just ONCE per dimension per chart
2471 this._chartDimensionColor = function(label) {
2472 if(this.colors === null) this.chartColors();
2474 if(typeof this.colors_assigned[label] === 'undefined') {
2475 if(this.colors_available.length === 0) {
2476 var len = NETDATA.themes.current.colors.length;
2478 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2481 this.colors_assigned[label] = this.colors_available.shift();
2483 if(this.debug === true)
2484 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2487 if(this.debug === true)
2488 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2491 this.colors.push(this.colors_assigned[label]);
2492 return this.colors_assigned[label];
2495 this.chartColors = function() {
2496 if(this.colors !== null) return this.colors;
2499 this.colors_available = [];
2501 // add the standard colors
2502 var len = NETDATA.themes.current.colors.length;
2504 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2506 // add the user supplied colors
2507 var c = $(this.element).data('colors');
2508 // this.log('read colors: ' + c);
2509 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2510 if(typeof c !== 'string') {
2511 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2521 this.colors_available.unshift(c[len]);
2522 // this.log('adding color: ' + c[len]);
2531 this.legendUpdateDOM = function() {
2532 var needed = false, dim, keys, len, i;
2534 // check that the legend DOM is up to date for the downloaded dimensions
2535 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2536 // this.log('the legend does not have any series - requesting legend update');
2539 else if(this.data === null) {
2540 // this.log('the chart does not have any data - requesting legend update');
2543 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2547 var labels = this.data.dimension_names.toString();
2548 if(labels !== this.element_legend_childs.series.labels_key) {
2551 if(this.debug === true)
2552 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2556 if(needed === false) {
2557 // make sure colors available
2560 // do we have to update the current values?
2561 // we do this, only when the visible chart is current
2562 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2563 if(this.debug === true)
2564 this.log('chart is in latest position... updating values on legend...');
2566 //var labels = this.data.dimension_names;
2567 //var i = labels.length;
2569 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2573 if(this.colors === null) {
2574 // this is the first time we update the chart
2575 // let's assign colors to all dimensions
2576 if(this.library.track_colors() === true) {
2577 keys = Object.keys(this.chart.dimensions);
2579 for(i = 0; i < len ;i++)
2580 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2583 // we will re-generate the colors for the chart
2584 // based on the selected dimensions
2587 if(this.debug === true)
2588 this.log('updating Legend DOM');
2590 // mark all dimensions as invalid
2591 this.dimensions_visibility.invalidateAll();
2593 var genLabel = function(state, parent, dim, name, count) {
2594 var color = state._chartDimensionColor(name);
2596 var user_element = null;
2597 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2598 if(user_id === null)
2599 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2600 if(user_id !== null) {
2601 user_element = document.getElementById(user_id) || null;
2602 if (user_element === null)
2603 state.log('Cannot find element with id: ' + user_id);
2606 state.element_legend_childs.series[name] = {
2607 name: document.createElement('span'),
2608 value: document.createElement('span'),
2611 last_shown_value: null
2614 var label = state.element_legend_childs.series[name];
2616 // create the dimension visibility tracking for this label
2617 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2619 var rgb = NETDATA.colorHex2Rgb(color);
2620 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2621 + state.chart.chart_type
2622 + '" style="background-color: '
2623 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2624 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2626 var text = document.createTextNode(' ' + name);
2627 label.name.appendChild(text);
2630 parent.appendChild(document.createElement('br'));
2632 parent.appendChild(label.name);
2633 parent.appendChild(label.value);
2636 var content = document.createElement('div');
2638 if(this.hasLegend()) {
2639 this.element_legend_childs = {
2641 resize_handler: document.createElement('div'),
2642 toolbox: document.createElement('div'),
2643 toolbox_left: document.createElement('div'),
2644 toolbox_right: document.createElement('div'),
2645 toolbox_reset: document.createElement('div'),
2646 toolbox_zoomin: document.createElement('div'),
2647 toolbox_zoomout: document.createElement('div'),
2648 toolbox_volume: document.createElement('div'),
2649 title_date: document.createElement('span'),
2650 title_time: document.createElement('span'),
2651 title_units: document.createElement('span'),
2652 perfect_scroller: document.createElement('div'),
2656 this.element_legend.innerHTML = '';
2658 if(this.library.toolboxPanAndZoom !== null) {
2660 var get_pan_and_zoom_step = function(event) {
2662 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2664 else if (event.shiftKey)
2665 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2667 else if (event.altKey)
2668 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2671 return NETDATA.options.current.pan_and_zoom_factor;
2674 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2675 this.element.appendChild(this.element_legend_childs.toolbox);
2677 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2678 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2679 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2680 this.element_legend_childs.toolbox_left.onclick = function(e) {
2683 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2684 var before = that.view_before - step;
2685 var after = that.view_after - step;
2686 if(after >= that.netdata_first)
2687 that.library.toolboxPanAndZoom(that, after, before);
2689 if(NETDATA.options.current.show_help === true)
2690 $(this.element_legend_childs.toolbox_left).popover({
2695 placement: 'bottom',
2696 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2698 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>'
2702 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2703 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2704 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2705 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2707 NETDATA.resetAllCharts(that);
2709 if(NETDATA.options.current.show_help === true)
2710 $(this.element_legend_childs.toolbox_reset).popover({
2715 placement: 'bottom',
2716 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2717 title: 'Chart Reset',
2718 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>'
2721 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2722 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2723 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2724 this.element_legend_childs.toolbox_right.onclick = function(e) {
2726 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2727 var before = that.view_before + step;
2728 var after = that.view_after + step;
2729 if(before <= that.netdata_last)
2730 that.library.toolboxPanAndZoom(that, after, before);
2732 if(NETDATA.options.current.show_help === true)
2733 $(this.element_legend_childs.toolbox_right).popover({
2738 placement: 'bottom',
2739 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2741 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>'
2745 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2746 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2747 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2748 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2750 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2751 var before = that.view_before - dt;
2752 var after = that.view_after + dt;
2753 that.library.toolboxPanAndZoom(that, after, before);
2755 if(NETDATA.options.current.show_help === true)
2756 $(this.element_legend_childs.toolbox_zoomin).popover({
2761 placement: 'bottom',
2762 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2763 title: 'Chart Zoom In',
2764 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>'
2767 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2768 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2769 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2770 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2772 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);
2773 var before = that.view_before + dt;
2774 var after = that.view_after - dt;
2776 that.library.toolboxPanAndZoom(that, after, before);
2778 if(NETDATA.options.current.show_help === true)
2779 $(this.element_legend_childs.toolbox_zoomout).popover({
2784 placement: 'bottom',
2785 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2786 title: 'Chart Zoom Out',
2787 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>'
2790 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2791 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2792 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2793 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2794 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2795 //e.preventDefault();
2796 //alert('clicked toolbox_volume on ' + that.id);
2800 this.element_legend_childs.toolbox = null;
2801 this.element_legend_childs.toolbox_left = null;
2802 this.element_legend_childs.toolbox_reset = null;
2803 this.element_legend_childs.toolbox_right = null;
2804 this.element_legend_childs.toolbox_zoomin = null;
2805 this.element_legend_childs.toolbox_zoomout = null;
2806 this.element_legend_childs.toolbox_volume = null;
2809 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2810 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2811 this.element.appendChild(this.element_legend_childs.resize_handler);
2812 if(NETDATA.options.current.show_help === true)
2813 $(this.element_legend_childs.resize_handler).popover({
2818 placement: 'bottom',
2819 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2820 title: 'Chart Resize',
2821 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>'
2825 this.element_legend_childs.resize_handler.onmousedown =
2827 that.resizeHandler(e);
2831 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2832 that.resizeHandler(e);
2835 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2836 this.element_legend.appendChild(this.element_legend_childs.title_date);
2837 this.__last_shown_legend_date = undefined;
2839 this.element_legend.appendChild(document.createElement('br'));
2841 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2842 this.element_legend.appendChild(this.element_legend_childs.title_time);
2843 this.__last_shown_legend_time = undefined;
2845 this.element_legend.appendChild(document.createElement('br'));
2847 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2848 this.element_legend.appendChild(this.element_legend_childs.title_units);
2849 this.__last_shown_legend_units = undefined;
2851 this.element_legend.appendChild(document.createElement('br'));
2853 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2854 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2856 content.className = 'netdata-legend-series-content';
2857 this.element_legend_childs.perfect_scroller.appendChild(content);
2859 if(NETDATA.options.current.show_help === true)
2860 $(content).popover({
2865 placement: 'bottom',
2866 title: 'Chart Legend',
2867 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2868 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>'
2872 this.element_legend_childs = {
2874 resize_handler: null,
2877 toolbox_right: null,
2878 toolbox_reset: null,
2879 toolbox_zoomin: null,
2880 toolbox_zoomout: null,
2881 toolbox_volume: null,
2885 perfect_scroller: null,
2891 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2892 if(this.debug === true)
2893 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2895 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2896 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2901 keys = Object.keys(this.chart.dimensions);
2902 for(i = 0, len = keys.length; i < len ;i++) {
2904 tmp.push(this.chart.dimensions[dim].name);
2905 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2907 this.element_legend_childs.series.labels_key = tmp.toString();
2908 if(this.debug === true)
2909 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2912 // create a hidden div to be used for hidding
2913 // the original legend of the chart library
2914 var el = document.createElement('div');
2915 if(this.element_legend !== null)
2916 this.element_legend.appendChild(el);
2917 el.style.display = 'none';
2919 this.element_legend_childs.hidden = document.createElement('div');
2920 el.appendChild(this.element_legend_childs.hidden);
2922 if(this.element_legend_childs.perfect_scroller !== null) {
2923 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2925 wheelPropagation: true,
2926 swipePropagation: true,
2927 minScrollbarLength: null,
2928 maxScrollbarLength: null,
2929 useBothWheelAxes: false,
2930 suppressScrollX: true,
2931 suppressScrollY: false,
2932 scrollXMarginOffset: 0,
2933 scrollYMarginOffset: 0,
2936 Ps.update(this.element_legend_childs.perfect_scroller);
2939 this.legendShowLatestValues();
2942 this.hasLegend = function() {
2943 if(typeof this.___hasLegendCache___ !== 'undefined')
2944 return this.___hasLegendCache___;
2947 if(this.library && this.library.legend(this) === 'right-side') {
2948 var legend = $(this.element).data('legend') || 'yes';
2949 if(legend === 'yes') leg = true;
2952 this.___hasLegendCache___ = leg;
2956 this.legendWidth = function() {
2957 return (this.hasLegend())?140:0;
2960 this.legendHeight = function() {
2961 return $(this.element).height();
2964 this.chartWidth = function() {
2965 return $(this.element).width() - this.legendWidth();
2968 this.chartHeight = function() {
2969 return $(this.element).height();
2972 this.chartPixelsPerPoint = function() {
2973 // force an options provided detail
2974 var px = this.pixels_per_point;
2976 if(this.library && px < this.library.pixels_per_point(this))
2977 px = this.library.pixels_per_point(this);
2979 if(px < NETDATA.options.current.pixels_per_point)
2980 px = NETDATA.options.current.pixels_per_point;
2985 this.needsRecreation = function() {
2987 this.chart_created === true
2989 && this.library.autoresize() === false
2990 && this.tm.last_resized < NETDATA.options.last_resized
2994 this.chartURL = function() {
2995 var after, before, points_multiplier = 1;
2996 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2997 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2999 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
3000 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
3001 this.view_after = after * 1000;
3002 this.view_before = before * 1000;
3004 this.requested_padding = null;
3005 points_multiplier = 1;
3007 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
3008 this.tm.pan_and_zoom_seq = 0;
3010 before = Math.round(this.current.force_before_ms / 1000);
3011 after = Math.round(this.current.force_after_ms / 1000);
3012 this.view_after = after * 1000;
3013 this.view_before = before * 1000;
3015 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
3016 this.requested_padding = Math.round((before - after) / 2);
3017 after -= this.requested_padding;
3018 before += this.requested_padding;
3019 this.requested_padding *= 1000;
3020 points_multiplier = 2;
3023 this.current.force_before_ms = null;
3024 this.current.force_after_ms = null;
3027 this.tm.pan_and_zoom_seq = 0;
3029 before = this.before;
3031 this.view_after = after * 1000;
3032 this.view_before = before * 1000;
3034 this.requested_padding = null;
3035 points_multiplier = 1;
3038 this.requested_after = after * 1000;
3039 this.requested_before = before * 1000;
3041 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3043 // build the data URL
3044 this.data_url = this.host + this.chart.data_url;
3045 this.data_url += "&format=" + this.library.format();
3046 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
3047 this.data_url += "&group=" + this.method;
3049 if(this.override_options !== null)
3050 this.data_url += "&options=" + this.override_options.toString();
3052 this.data_url += "&options=" + this.library.options(this);
3054 this.data_url += '|jsonwrap';
3056 if(NETDATA.options.current.eliminate_zero_dimensions === true)
3057 this.data_url += '|nonzero';
3059 if(this.append_options !== null)
3060 this.data_url += '|' + this.append_options.toString();
3063 this.data_url += "&after=" + after.toString();
3066 this.data_url += "&before=" + before.toString();
3069 this.data_url += "&dimensions=" + this.dimensions;
3071 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3072 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3075 this.redrawChart = function() {
3076 if(this.data !== null)
3077 this.updateChartWithData(this.data);
3080 this.updateChartWithData = function(data) {
3081 if(this.debug === true)
3082 this.log('updateChartWithData() called.');
3084 // this may force the chart to be re-created
3088 this.updates_counter++;
3089 this.updates_since_last_unhide++;
3090 this.updates_since_last_creation++;
3092 var started = Date.now();
3094 // if the result is JSON, find the latest update-every
3095 this.data_update_every = data.view_update_every * 1000;
3096 this.data_after = data.after * 1000;
3097 this.data_before = data.before * 1000;
3098 this.netdata_first = data.first_entry * 1000;
3099 this.netdata_last = data.last_entry * 1000;
3100 this.data_points = data.points;
3103 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3104 if(this.view_after < this.data_after) {
3105 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3106 this.view_after = this.data_after;
3109 if(this.view_before > this.data_before) {
3110 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3111 this.view_before = this.data_before;
3115 this.view_after = this.data_after;
3116 this.view_before = this.data_before;
3119 if(this.debug === true) {
3120 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3122 if(this.current.force_after_ms)
3123 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3125 this.log('STATUS: forced : unset');
3127 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3128 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3129 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3130 this.log('STATUS: points : ' + (this.data_points).toString());
3133 if(this.data_points === 0) {
3138 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3139 if(this.debug === true)
3140 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3142 this.chart_created = false;
3145 // check and update the legend
3146 this.legendUpdateDOM();
3148 if(this.chart_created === true
3149 && typeof this.library.update === 'function') {
3151 if(this.debug === true)
3152 this.log('updating chart...');
3154 if(callChartLibraryUpdateSafely(data) === false)
3158 if(this.debug === true)
3159 this.log('creating chart...');
3161 if(callChartLibraryCreateSafely(data) === false)
3165 this.legendShowLatestValues();
3166 if(this.selected === true)
3167 NETDATA.globalSelectionSync.stop();
3169 // update the performance counters
3170 var now = Date.now();
3171 this.tm.last_updated = now;
3173 // don't update last_autorefreshed if this chart is
3174 // forced to be updated with global PanAndZoom
3175 if(NETDATA.globalPanAndZoom.isActive())
3176 this.tm.last_autorefreshed = 0;
3178 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3179 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3181 this.tm.last_autorefreshed = now;
3184 this.refresh_dt_ms = now - started;
3185 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3187 if(this.refresh_dt_element !== null)
3188 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3191 this.updateChart = function(callback) {
3192 if(this.debug === true)
3193 this.log('updateChart() called.');
3195 if(this._updating === true) {
3196 if(this.debug === true)
3197 this.log('I am already updating...');
3199 if(typeof callback === 'function')
3205 // due to late initialization of charts and libraries
3206 // we need to check this too
3207 if(this.enabled === false) {
3208 if(this.debug === true)
3209 this.log('I am not enabled');
3211 if(typeof callback === 'function')
3217 if(canBeRendered() === false) {
3218 if(typeof callback === 'function')
3224 if(this.chart === null)
3225 return this.getChart(function() {
3226 return that.updateChart(callback);
3229 if(this.library.initialized === false) {
3230 if(this.library.enabled === true) {
3231 return this.library.initialize(function () {
3232 return that.updateChart(callback);
3236 error('chart library "' + this.library_name + '" is not available.');
3238 if(typeof callback === 'function')
3245 this.clearSelection();
3248 if(this.debug === true)
3249 this.log('updating from ' + this.data_url);
3251 NETDATA.statistics.refreshes_total++;
3252 NETDATA.statistics.refreshes_active++;
3254 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3255 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3257 this._updating = true;
3259 this.xhr = $.ajax( {
3264 'Cache-Control': 'no-cache, no-store',
3265 'Pragma': 'no-cache'
3267 xhrFields: { withCredentials: true } // required for the cookie
3269 .done(function(data) {
3270 that.xhr = undefined;
3271 that.retries_on_data_failures = 0;
3273 if(that.debug === true)
3274 that.log('data received. updating chart.');
3276 that.updateChartWithData(data);
3278 .fail(function(msg) {
3279 that.xhr = undefined;
3281 if(msg.statusText !== 'abort') {
3282 that.retries_on_data_failures++;
3283 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3284 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3285 that.retries_on_data_failures = 0;
3286 error('data download failed for url: ' + that.data_url);
3289 that.tm.last_autorefreshed = Date.now();
3290 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3294 .always(function() {
3295 that.xhr = undefined;
3297 NETDATA.statistics.refreshes_active--;
3298 that._updating = false;
3300 if(typeof callback === 'function')
3305 this.isVisible = function(nocache) {
3306 if(typeof nocache === 'undefined')
3309 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3311 // caching - we do not evaluate the charts visibility
3312 // if the page has not been scrolled since the last check
3313 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3314 return this.___isVisible___;
3316 this.tm.last_visible_check = Date.now();
3318 var wh = window.innerHeight;
3319 var x = this.element.getBoundingClientRect();
3323 if(x.width === 0 || x.height === 0) {
3325 this.___isVisible___ = false;
3326 return this.___isVisible___;
3329 if(x.top < 0 && -x.top > x.height) {
3330 // the chart is entirely above
3331 ret = -x.top - x.height;
3333 else if(x.top > wh) {
3334 // the chart is entirely below
3338 if(ret > tolerance) {
3339 // the chart is too far
3342 this.___isVisible___ = false;
3343 return this.___isVisible___;
3346 // the chart is inside or very close
3349 this.___isVisible___ = true;
3350 return this.___isVisible___;
3354 this.isAutoRefreshable = function() {
3355 return (this.current.autorefresh);
3358 this.canBeAutoRefreshed = function() {
3359 var now = Date.now();
3361 if(this.running === true) {
3362 if(this.debug === true)
3363 this.log('I am already running');
3368 if(this.enabled === false) {
3369 if(this.debug === true)
3370 this.log('I am not enabled');
3375 if(this.library === null || this.library.enabled === false) {
3376 error('charting library "' + this.library_name + '" is not available');
3377 if(this.debug === true)
3378 this.log('My chart library ' + this.library_name + ' is not available');
3383 if(this.isVisible() === false) {
3384 if(NETDATA.options.debug.visibility === true || this.debug === true)
3385 this.log('I am not visible');
3390 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3391 if(this.debug === true)
3392 this.log('timed force update detected - allowing this update');
3394 this.current.force_update_at = 0;
3398 if(this.isAutoRefreshable() === true) {
3399 // allow the first update, even if the page is not visible
3400 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3401 if(NETDATA.options.debug.focus === true || this.debug === true)
3402 this.log('canBeAutoRefreshed(): page does not have focus');
3407 if(this.needsRecreation() === true) {
3408 if(this.debug === true)
3409 this.log('canBeAutoRefreshed(): needs re-creation.');
3414 // options valid only for autoRefresh()
3415 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3416 if(NETDATA.globalPanAndZoom.isActive()) {
3417 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3418 if(this.debug === true)
3419 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3424 if(this.debug === true)
3425 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3431 if(this.selected === true) {
3432 if(this.debug === true)
3433 this.log('canBeAutoRefreshed(): I have a selection in place.');
3438 if(this.paused === true) {
3439 if(this.debug === true)
3440 this.log('canBeAutoRefreshed(): I am paused.');
3445 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3446 if(this.debug === true)
3447 this.log('canBeAutoRefreshed(): It is time to update me.');
3457 this.autoRefresh = function(callback) {
3458 if(this.canBeAutoRefreshed() === true && this.running === false) {
3461 state.running = true;
3462 state.updateChart(function() {
3463 state.running = false;
3465 if(typeof callback !== 'undefined')
3470 if(typeof callback !== 'undefined')
3475 this._defaultsFromDownloadedChart = function(chart) {
3477 this.chart_url = chart.url;
3478 this.data_update_every = chart.update_every * 1000;
3479 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3480 this.tm.last_info_downloaded = Date.now();
3482 if(this.title === null)
3483 this.title = chart.title;
3485 if(this.units === null)
3486 this.units = chart.units;
3489 // fetch the chart description from the netdata server
3490 this.getChart = function(callback) {
3491 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3493 this._defaultsFromDownloadedChart(this.chart);
3495 if(typeof callback === 'function')
3499 this.chart_url = "/api/v1/chart?chart=" + this.id;
3501 if(this.debug === true)
3502 this.log('downloading ' + this.chart_url);
3505 url: this.host + this.chart_url,
3508 xhrFields: { withCredentials: true } // required for the cookie
3510 .done(function(chart) {
3511 chart.url = that.chart_url;
3512 that._defaultsFromDownloadedChart(chart);
3513 NETDATA.chartRegistry.add(that.host, that.id, chart);
3516 NETDATA.error(404, that.chart_url);
3517 error('chart not found on url "' + that.chart_url + '"');
3519 .always(function() {
3520 if(typeof callback === 'function')
3526 // ============================================================================================================
3532 NETDATA.resetAllCharts = function(state) {
3533 // first clear the global selection sync
3534 // to make sure no chart is in selected state
3535 state.globalSelectionSyncStop();
3537 // there are 2 possibilities here
3538 // a. state is the global Pan and Zoom master
3539 // b. state is not the global Pan and Zoom master
3541 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3544 // clear the global Pan and Zoom
3545 // this will also refresh the master
3546 // and unblock any charts currently mirroring the master
3547 NETDATA.globalPanAndZoom.clearMaster();
3549 // if we were not the master, reset our status too
3550 // this is required because most probably the mouse
3551 // is over this chart, blocking it from auto-refreshing
3552 if(master === false && (state.paused === true || state.selected === true))
3556 // get or create a chart state, given a DOM element
3557 NETDATA.chartState = function(element) {
3558 var state = $(element).data('netdata-state-object') || null;
3559 if(state === null) {
3560 state = new chartState(element);
3561 $(element).data('netdata-state-object', state);
3566 // ----------------------------------------------------------------------------------------------------------------
3567 // Library functions
3569 // Load a script without jquery
3570 // This is used to load jquery - after it is loaded, we use jquery
3571 NETDATA._loadjQuery = function(callback) {
3572 if(typeof jQuery === 'undefined') {
3573 if(NETDATA.options.debug.main_loop === true)
3574 console.log('loading ' + NETDATA.jQuery);
3576 var script = document.createElement('script');
3577 script.type = 'text/javascript';
3578 script.async = true;
3579 script.src = NETDATA.jQuery;
3581 // script.onabort = onError;
3582 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3583 if(typeof callback === "function")
3584 script.onload = callback;
3586 var s = document.getElementsByTagName('script')[0];
3587 s.parentNode.insertBefore(script, s);
3589 else if(typeof callback === "function")
3593 NETDATA._loadCSS = function(filename) {
3594 // don't use jQuery here
3595 // styles are loaded before jQuery
3596 // to eliminate showing an unstyled page to the user
3598 var fileref = document.createElement("link");
3599 fileref.setAttribute("rel", "stylesheet");
3600 fileref.setAttribute("type", "text/css");
3601 fileref.setAttribute("href", filename);
3603 if (typeof fileref !== 'undefined')
3604 document.getElementsByTagName("head")[0].appendChild(fileref);
3607 NETDATA.colorHex2Rgb = function(hex) {
3608 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3609 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3610 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3611 return r + r + g + g + b + b;
3614 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3616 r: parseInt(result[1], 16),
3617 g: parseInt(result[2], 16),
3618 b: parseInt(result[3], 16)
3622 NETDATA.colorLuminance = function(hex, lum) {
3623 // validate hex string
3624 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3626 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3630 // convert to decimal and change luminosity
3631 var rgb = "#", c, i;
3632 for (i = 0; i < 3; i++) {
3633 c = parseInt(hex.substr(i*2,2), 16);
3634 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3635 rgb += ("00"+c).substr(c.length);
3641 NETDATA.guid = function() {
3643 return Math.floor((1 + Math.random()) * 0x10000)
3648 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3651 NETDATA.zeropad = function(x) {
3652 if(x > -10 && x < 10) return '0' + x.toString();
3653 else return x.toString();
3656 // user function to signal us the DOM has been
3658 NETDATA.updatedDom = function() {
3659 NETDATA.options.updated_dom = true;
3662 NETDATA.ready = function(callback) {
3663 NETDATA.options.pauseCallback = callback;
3666 NETDATA.pause = function(callback) {
3667 if(typeof callback === 'function') {
3668 if (NETDATA.options.pause === true)
3671 NETDATA.options.pauseCallback = callback;
3675 NETDATA.unpause = function() {
3676 NETDATA.options.pauseCallback = null;
3677 NETDATA.options.updated_dom = true;
3678 NETDATA.options.pause = false;
3681 // ----------------------------------------------------------------------------------------------------------------
3683 // this is purely sequential charts refresher
3684 // it is meant to be autonomous
3685 NETDATA.chartRefresherNoParallel = function(index) {
3686 if(NETDATA.options.debug.main_loop === true)
3687 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3689 if(NETDATA.options.updated_dom === true) {
3690 // the dom has been updated
3691 // get the dom parts again
3692 NETDATA.parseDom(NETDATA.chartRefresher);
3695 if(index >= NETDATA.options.targets.length) {
3696 if(NETDATA.options.debug.main_loop === true)
3697 console.log('waiting to restart main loop...');
3699 NETDATA.options.auto_refresher_fast_weight = 0;
3701 setTimeout(function() {
3702 NETDATA.chartRefresher();
3703 }, NETDATA.options.current.idle_between_loops);
3706 var state = NETDATA.options.targets[index];
3708 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3709 if(NETDATA.options.debug.main_loop === true)
3710 console.log('fast rendering...');
3712 state.autoRefresh(function() {
3713 NETDATA.chartRefresherNoParallel(++index);
3717 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3718 NETDATA.options.auto_refresher_fast_weight = 0;
3720 setTimeout(function() {
3721 state.autoRefresh(function() {
3722 NETDATA.chartRefresherNoParallel(++index);
3724 }, NETDATA.options.current.idle_between_charts);
3729 NETDATA.chartRefresherWaitTime = function() {
3730 return NETDATA.options.current.idle_parallel_loops;
3733 // the default refresher
3734 NETDATA.chartRefresher = function() {
3735 // console.log('auto-refresher...');
3737 if(NETDATA.options.pause === true) {
3738 // console.log('auto-refresher is paused');
3739 setTimeout(NETDATA.chartRefresher,
3740 NETDATA.chartRefresherWaitTime());
3744 if(typeof NETDATA.options.pauseCallback === 'function') {
3745 // console.log('auto-refresher is calling pauseCallback');
3746 NETDATA.options.pause = true;
3747 NETDATA.options.pauseCallback();
3748 NETDATA.chartRefresher();
3752 if(NETDATA.options.current.parallel_refresher === false) {
3753 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3754 NETDATA.chartRefresherNoParallel(0);
3758 if(NETDATA.options.updated_dom === true) {
3759 // the dom has been updated
3760 // get the dom parts again
3761 // console.log('auto-refresher is calling parseDom()');
3762 NETDATA.parseDom(NETDATA.chartRefresher);
3767 var targets = NETDATA.options.targets;
3768 var len = targets.length;
3771 state = targets[len];
3772 if(state.isVisible() === false || state.running === true)
3775 if(state.library.initialized === false) {
3776 if(state.library.enabled === true) {
3777 state.library.initialize(NETDATA.chartRefresher);
3781 state.error('chart library "' + state.library_name + '" is not enabled.');
3785 parallel.unshift(state);
3788 if(parallel.length > 0) {
3789 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3790 // this will execute the jobs in parallel
3791 $(parallel).each(function() {
3796 // console.log('auto-refresher nothing to do');
3799 // run the next refresh iteration
3800 setTimeout(NETDATA.chartRefresher,
3801 NETDATA.chartRefresherWaitTime());
3804 NETDATA.parseDom = function(callback) {
3805 NETDATA.options.last_page_scroll = Date.now();
3806 NETDATA.options.updated_dom = false;
3808 var targets = $('div[data-netdata]'); //.filter(':visible');
3810 if(NETDATA.options.debug.main_loop === true)
3811 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3813 NETDATA.options.targets = [];
3814 var len = targets.length;
3816 // the initialization will take care of sizing
3817 // and the "loading..." message
3818 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3821 if(typeof callback === 'function')
3825 // this is the main function - where everything starts
3826 NETDATA.start = function() {
3827 // this should be called only once
3829 NETDATA.options.page_is_visible = true;
3831 $(window).blur(function() {
3832 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3833 NETDATA.options.page_is_visible = false;
3834 if(NETDATA.options.debug.focus === true)
3835 console.log('Lost Focus!');
3839 $(window).focus(function() {
3840 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3841 NETDATA.options.page_is_visible = true;
3842 if(NETDATA.options.debug.focus === true)
3843 console.log('Focus restored!');
3847 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3848 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3849 NETDATA.options.page_is_visible = false;
3850 if(NETDATA.options.debug.focus === true)
3851 console.log('Document has no focus!');
3855 // bootstrap tab switching
3856 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3858 // bootstrap modal switching
3859 var $modal = $('.modal');
3860 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3861 $modal.on('shown.bs.modal', NETDATA.onscroll);
3863 // bootstrap collapse switching
3864 var $collapse = $('.collapse');
3865 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3866 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3868 NETDATA.parseDom(NETDATA.chartRefresher);
3870 // Alarms initialization
3871 setTimeout(NETDATA.alarms.init, 1000);
3873 // Registry initialization
3874 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3876 if(typeof netdataCallback === 'function')
3880 // ----------------------------------------------------------------------------------------------------------------
3883 NETDATA.peityInitialize = function(callback) {
3884 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3886 url: NETDATA.peity_js,
3889 xhrFields: { withCredentials: true } // required for the cookie
3892 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3895 NETDATA.chartLibraries.peity.enabled = false;
3896 NETDATA.error(100, NETDATA.peity_js);
3898 .always(function() {
3899 if(typeof callback === "function")
3904 NETDATA.chartLibraries.peity.enabled = false;
3905 if(typeof callback === "function")
3910 NETDATA.peityChartUpdate = function(state, data) {
3911 state.peity_instance.innerHTML = data.result;
3913 if(state.peity_options.stroke !== state.chartColors()[0]) {
3914 state.peity_options.stroke = state.chartColors()[0];
3915 if(state.chart.chart_type === 'line')
3916 state.peity_options.fill = NETDATA.themes.current.background;
3918 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3921 $(state.peity_instance).peity('line', state.peity_options);
3925 NETDATA.peityChartCreate = function(state, data) {
3926 state.peity_instance = document.createElement('div');
3927 state.element_chart.appendChild(state.peity_instance);
3929 var self = $(state.element);
3930 state.peity_options = {
3931 stroke: NETDATA.themes.current.foreground,
3932 strokeWidth: self.data('peity-strokewidth') || 1,
3933 width: state.chartWidth(),
3934 height: state.chartHeight(),
3935 fill: NETDATA.themes.current.foreground
3938 NETDATA.peityChartUpdate(state, data);
3942 // ----------------------------------------------------------------------------------------------------------------
3945 NETDATA.sparklineInitialize = function(callback) {
3946 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3948 url: NETDATA.sparkline_js,
3951 xhrFields: { withCredentials: true } // required for the cookie
3954 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3957 NETDATA.chartLibraries.sparkline.enabled = false;
3958 NETDATA.error(100, NETDATA.sparkline_js);
3960 .always(function() {
3961 if(typeof callback === "function")
3966 NETDATA.chartLibraries.sparkline.enabled = false;
3967 if(typeof callback === "function")
3972 NETDATA.sparklineChartUpdate = function(state, data) {
3973 state.sparkline_options.width = state.chartWidth();
3974 state.sparkline_options.height = state.chartHeight();
3976 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3980 NETDATA.sparklineChartCreate = function(state, data) {
3981 var self = $(state.element);
3982 var type = self.data('sparkline-type') || 'line';
3983 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3984 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3985 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3986 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3987 var composite = self.data('sparkline-composite') || undefined;
3988 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3989 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3990 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3991 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3992 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3993 var spotColor = self.data('sparkline-spotcolor') || undefined;
3994 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3995 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3996 var spotRadius = self.data('sparkline-spotradius') || undefined;
3997 var valueSpots = self.data('sparkline-valuespots') || undefined;
3998 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3999 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
4000 var lineWidth = self.data('sparkline-linewidth') || undefined;
4001 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
4002 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
4003 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
4004 var xvalues = self.data('sparkline-xvalues') || undefined;
4005 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
4006 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
4007 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
4008 var disableInteraction = self.data('sparkline-disableinteraction') || false;
4009 var disableTooltips = self.data('sparkline-disabletooltips') || false;
4010 var disableHighlight = self.data('sparkline-disablehighlight') || false;
4011 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
4012 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
4013 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
4014 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
4015 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
4016 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
4017 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
4018 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
4019 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
4020 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
4021 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
4022 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
4023 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
4024 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
4025 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
4026 var animatedZooms = self.data('sparkline-animatedzooms') || false;
4028 if(spotColor === 'disable') spotColor='';
4029 if(minSpotColor === 'disable') minSpotColor='';
4030 if(maxSpotColor === 'disable') maxSpotColor='';
4032 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
4034 state.sparkline_options = {
4036 lineColor: lineColor,
4037 fillColor: fillColor,
4038 chartRangeMin: chartRangeMin,
4039 chartRangeMax: chartRangeMax,
4040 composite: composite,
4041 enableTagOptions: enableTagOptions,
4042 tagOptionPrefix: tagOptionPrefix,
4043 tagValuesAttribute: tagValuesAttribute,
4044 disableHiddenCheck: disableHiddenCheck,
4045 defaultPixelsPerValue: defaultPixelsPerValue,
4046 spotColor: spotColor,
4047 minSpotColor: minSpotColor,
4048 maxSpotColor: maxSpotColor,
4049 spotRadius: spotRadius,
4050 valueSpots: valueSpots,
4051 highlightSpotColor: highlightSpotColor,
4052 highlightLineColor: highlightLineColor,
4053 lineWidth: lineWidth,
4054 normalRangeMin: normalRangeMin,
4055 normalRangeMax: normalRangeMax,
4056 drawNormalOnTop: drawNormalOnTop,
4058 chartRangeClip: chartRangeClip,
4059 chartRangeMinX: chartRangeMinX,
4060 chartRangeMaxX: chartRangeMaxX,
4061 disableInteraction: disableInteraction,
4062 disableTooltips: disableTooltips,
4063 disableHighlight: disableHighlight,
4064 highlightLighten: highlightLighten,
4065 highlightColor: highlightColor,
4066 tooltipContainer: tooltipContainer,
4067 tooltipClassname: tooltipClassname,
4068 tooltipChartTitle: state.title,
4069 tooltipFormat: tooltipFormat,
4070 tooltipPrefix: tooltipPrefix,
4071 tooltipSuffix: tooltipSuffix,
4072 tooltipSkipNull: tooltipSkipNull,
4073 tooltipValueLookups: tooltipValueLookups,
4074 tooltipFormatFieldlist: tooltipFormatFieldlist,
4075 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4076 numberFormatter: numberFormatter,
4077 numberDigitGroupSep: numberDigitGroupSep,
4078 numberDecimalMark: numberDecimalMark,
4079 numberDigitGroupCount: numberDigitGroupCount,
4080 animatedZooms: animatedZooms,
4081 width: state.chartWidth(),
4082 height: state.chartHeight()
4085 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4089 // ----------------------------------------------------------------------------------------------------------------
4096 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4097 if(after < state.netdata_first)
4098 after = state.netdata_first;
4100 if(before > state.netdata_last)
4101 before = state.netdata_last;
4103 state.setMode('zoom');
4104 state.globalSelectionSyncStop();
4105 state.globalSelectionSyncDelay();
4106 state.dygraph_user_action = true;
4107 state.dygraph_force_zoom = true;
4108 state.updateChartPanOrZoom(after, before);
4109 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4112 NETDATA.dygraphSetSelection = function(state, t) {
4113 if(typeof state.dygraph_instance !== 'undefined') {
4114 var r = state.calculateRowForTime(t);
4116 state.dygraph_instance.setSelection(r);
4118 state.dygraph_instance.clearSelection();
4119 state.legendShowUndefined();
4126 NETDATA.dygraphClearSelection = function(state) {
4127 if(typeof state.dygraph_instance !== 'undefined') {
4128 state.dygraph_instance.clearSelection();
4133 NETDATA.dygraphSmoothInitialize = function(callback) {
4135 url: NETDATA.dygraph_smooth_js,
4138 xhrFields: { withCredentials: true } // required for the cookie
4141 NETDATA.dygraph.smooth = true;
4142 smoothPlotter.smoothing = 0.3;
4145 NETDATA.dygraph.smooth = false;
4147 .always(function() {
4148 if(typeof callback === "function")
4153 NETDATA.dygraphInitialize = function(callback) {
4154 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4156 url: NETDATA.dygraph_js,
4159 xhrFields: { withCredentials: true } // required for the cookie
4162 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4165 NETDATA.chartLibraries.dygraph.enabled = false;
4166 NETDATA.error(100, NETDATA.dygraph_js);
4168 .always(function() {
4169 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4170 NETDATA.dygraphSmoothInitialize(callback);
4171 else if(typeof callback === "function")
4176 NETDATA.chartLibraries.dygraph.enabled = false;
4177 if(typeof callback === "function")
4182 NETDATA.dygraphChartUpdate = function(state, data) {
4183 var dygraph = state.dygraph_instance;
4185 if(typeof dygraph === 'undefined')
4186 return NETDATA.dygraphChartCreate(state, data);
4188 // when the chart is not visible, and hidden
4189 // if there is a window resize, dygraph detects
4190 // its element size as 0x0.
4191 // this will make it re-appear properly
4193 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4197 file: data.result.data,
4198 colors: state.chartColors(),
4199 labels: data.result.labels,
4200 labelsDivWidth: state.chartWidth() - 70,
4201 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4204 if(state.dygraph_force_zoom === true) {
4205 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4206 state.log('dygraphChartUpdate() forced zoom update');
4208 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4209 options.isZoomedIgnoreProgrammaticZoom = true;
4210 state.dygraph_force_zoom = false;
4212 else if(state.current.name !== 'auto') {
4213 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4214 state.log('dygraphChartUpdate() loose update');
4217 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4218 state.log('dygraphChartUpdate() strict update');
4220 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4221 options.isZoomedIgnoreProgrammaticZoom = true;
4224 options.valueRange = state.dygraph_options.valueRange;
4226 var oldMax = null, oldMin = null;
4227 if(state.__commonMin !== null) {
4228 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4229 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4231 if(state.__commonMax !== null) {
4232 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4233 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4236 if(state.dygraph_smooth_eligible === true) {
4237 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4238 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4239 NETDATA.dygraphChartCreate(state, data);
4244 dygraph.updateOptions(options);
4247 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4248 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4249 options.valueRange[0] = NETDATA.commonMin.get(state);
4252 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4253 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4254 options.valueRange[1] = NETDATA.commonMax.get(state);
4258 if(redraw === true) {
4259 // state.log('forcing redraw to adapt to common- min/max');
4260 dygraph.updateOptions(options);
4263 state.dygraph_last_rendered = Date.now();
4267 NETDATA.dygraphChartCreate = function(state, data) {
4268 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4269 state.log('dygraphChartCreate()');
4271 var self = $(state.element);
4273 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4274 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4276 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4278 var smooth = (NETDATA.dygraph.smooth === true)
4279 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4282 state.dygraph_options = {
4283 colors: self.data('dygraph-colors') || state.chartColors(),
4285 // leave a few pixels empty on the right of the chart
4286 rightGap: self.data('dygraph-rightgap')
4289 showRangeSelector: self.data('dygraph-showrangeselector')
4292 showRoller: self.data('dygraph-showroller')
4295 title: self.data('dygraph-title')
4298 titleHeight: self.data('dygraph-titleheight')
4301 legend: self.data('dygraph-legend')
4302 || 'always', // we need this to get selection events
4304 labels: data.result.labels,
4306 labelsDiv: self.data('dygraph-labelsdiv')
4307 || state.element_legend_childs.hidden,
4309 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4310 || { 'fontSize':'1px' },
4312 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4313 || state.chartWidth() - 70,
4315 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4318 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4324 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4327 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4330 includeZero: self.data('dygraph-includezero')
4331 || (chart_type === 'stacked'),
4333 xRangePad: self.data('dygraph-xrangepad')
4336 yRangePad: self.data('dygraph-yrangepad')
4339 valueRange: self.data('dygraph-valuerange')
4342 ylabel: state.units,
4344 yLabelWidth: self.data('dygraph-ylabelwidth')
4347 // the function to plot the chart
4350 // The width of the lines connecting data points.
4351 // This can be used to increase the contrast or some graphs.
4352 strokeWidth: self.data('dygraph-strokewidth')
4353 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4355 strokePattern: self.data('dygraph-strokepattern')
4358 // The size of the dot to draw on each point in pixels (see drawPoints).
4359 // A dot is always drawn when a point is "isolated",
4360 // i.e. there is a missing point on either side of it.
4361 // This also controls the size of those dots.
4362 drawPoints: self.data('dygraph-drawpoints')
4365 // Draw points at the edges of gaps in the data.
4366 // This improves visibility of small data segments or other data irregularities.
4367 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4370 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4373 pointSize: self.data('dygraph-pointsize')
4376 // enabling this makes the chart with little square lines
4377 stepPlot: self.data('dygraph-stepplot')
4380 // Draw a border around graph lines to make crossing lines more easily
4381 // distinguishable. Useful for graphs with many lines.
4382 strokeBorderColor: self.data('dygraph-strokebordercolor')
4383 || NETDATA.themes.current.background,
4385 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4386 || (chart_type === 'stacked')?0.0:0.0,
4388 fillGraph: self.data('dygraph-fillgraph')
4389 || (chart_type === 'area' || chart_type === 'stacked'),
4391 fillAlpha: self.data('dygraph-fillalpha')
4392 || ((chart_type === 'stacked')
4393 ?NETDATA.options.current.color_fill_opacity_stacked
4394 :NETDATA.options.current.color_fill_opacity_area),
4396 stackedGraph: self.data('dygraph-stackedgraph')
4397 || (chart_type === 'stacked'),
4399 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4402 drawAxis: self.data('dygraph-drawaxis')
4405 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4408 axisLineColor: self.data('dygraph-axislinecolor')
4409 || NETDATA.themes.current.axis,
4411 axisLineWidth: self.data('dygraph-axislinewidth')
4414 drawGrid: self.data('dygraph-drawgrid')
4417 gridLinePattern: self.data('dygraph-gridlinepattern')
4420 gridLineWidth: self.data('dygraph-gridlinewidth')
4423 gridLineColor: self.data('dygraph-gridlinecolor')
4424 || NETDATA.themes.current.grid,
4426 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4429 sigFigs: self.data('dygraph-sigfigs')
4432 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4435 valueFormatter: self.data('dygraph-valueformatter')
4438 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4439 || highlightCircleSize,
4441 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4442 || null, // TOO SLOW: { strokeWidth: 1.5 },
4444 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4445 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4447 pointClickCallback: self.data('dygraph-pointclickcallback')
4450 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4455 ticker: Dygraph.dateTicker,
4456 axisLabelFormatter: function (d, gran) {
4458 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4463 axisLabelFormatter: function (y) {
4465 // unfortunately, we have to call this every single time
4466 state.legendFormatValueDecimalsFromMinMax(
4467 this.axes_[0].extremeRange[0],
4468 this.axes_[0].extremeRange[1]
4471 return state.legendFormatValue(y);
4475 legendFormatter: function(data) {
4476 var elements = state.element_legend_childs;
4478 // if the hidden div is not there
4479 // we are not managing the legend
4480 if(elements.hidden === null) return;
4482 if (typeof data.x !== 'undefined') {
4483 state.legendSetDate(data.x);
4484 var i = data.series.length;
4486 var series = data.series[i];
4487 if(series.isVisible === true)
4488 state.legendSetLabelValue(series.label, series.y);
4490 state.legendSetLabelValue(series.label, null);
4496 drawCallback: function(dygraph, is_initial) {
4497 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4498 state.dygraph_user_action = false;
4500 var x_range = dygraph.xAxisRange();
4501 var after = Math.round(x_range[0]);
4502 var before = Math.round(x_range[1]);
4504 if(NETDATA.options.debug.dygraph === true)
4505 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4507 if(before <= state.netdata_last && after >= state.netdata_first)
4508 state.updateChartPanOrZoom(after, before);
4511 zoomCallback: function(minDate, maxDate, yRanges) {
4514 if(NETDATA.options.debug.dygraph === true)
4515 state.log('dygraphZoomCallback()');
4517 state.globalSelectionSyncStop();
4518 state.globalSelectionSyncDelay();
4519 state.setMode('zoom');
4521 // refresh it to the greatest possible zoom level
4522 state.dygraph_user_action = true;
4523 state.dygraph_force_zoom = true;
4524 state.updateChartPanOrZoom(minDate, maxDate);
4526 highlightCallback: function(event, x, points, row, seriesName) {
4529 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4530 state.log('dygraphHighlightCallback()');
4534 // there is a bug in dygraph when the chart is zoomed enough
4535 // the time it thinks is selected is wrong
4536 // here we calculate the time t based on the row number selected
4538 // var t = state.data_after + row * state.data_update_every;
4539 // 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);
4541 state.globalSelectionSync(x);
4543 // fix legend zIndex using the internal structures of dygraph legend module
4544 // this works, but it is a hack!
4545 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4547 unhighlightCallback: function(event) {
4550 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4551 state.log('dygraphUnhighlightCallback()');
4553 state.unpauseChart();
4554 state.globalSelectionSyncStop();
4556 interactionModel : {
4557 mousedown: function(event, dygraph, context) {
4558 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4559 state.log('interactionModel.mousedown()');
4561 state.dygraph_user_action = true;
4562 state.globalSelectionSyncStop();
4564 if(NETDATA.options.debug.dygraph === true)
4565 state.log('dygraphMouseDown()');
4567 // Right-click should not initiate a zoom.
4568 if(event.button && event.button === 2) return;
4570 context.initializeMouseDown(event, dygraph, context);
4572 if(event.button && event.button === 1) {
4573 if (event.altKey || event.shiftKey) {
4574 state.setMode('pan');
4575 state.globalSelectionSyncDelay();
4576 Dygraph.startPan(event, dygraph, context);
4579 state.setMode('zoom');
4580 state.globalSelectionSyncDelay();
4581 Dygraph.startZoom(event, dygraph, context);
4585 if (event.altKey || event.shiftKey) {
4586 state.setMode('zoom');
4587 state.globalSelectionSyncDelay();
4588 Dygraph.startZoom(event, dygraph, context);
4591 state.setMode('pan');
4592 state.globalSelectionSyncDelay();
4593 Dygraph.startPan(event, dygraph, context);
4597 mousemove: function(event, dygraph, context) {
4598 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4599 state.log('interactionModel.mousemove()');
4601 if(context.isPanning) {
4602 state.dygraph_user_action = true;
4603 state.globalSelectionSyncStop();
4604 state.globalSelectionSyncDelay();
4605 state.setMode('pan');
4606 context.is2DPan = false;
4607 Dygraph.movePan(event, dygraph, context);
4609 else if(context.isZooming) {
4610 state.dygraph_user_action = true;
4611 state.globalSelectionSyncStop();
4612 state.globalSelectionSyncDelay();
4613 state.setMode('zoom');
4614 Dygraph.moveZoom(event, dygraph, context);
4617 mouseup: function(event, dygraph, context) {
4618 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4619 state.log('interactionModel.mouseup()');
4621 if (context.isPanning) {
4622 state.dygraph_user_action = true;
4623 state.globalSelectionSyncDelay();
4624 Dygraph.endPan(event, dygraph, context);
4626 else if (context.isZooming) {
4627 state.dygraph_user_action = true;
4628 state.globalSelectionSyncDelay();
4629 Dygraph.endZoom(event, dygraph, context);
4632 click: function(event, dygraph, context) {
4636 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4637 state.log('interactionModel.click()');
4639 event.preventDefault();
4641 dblclick: function(event, dygraph, context) {
4646 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4647 state.log('interactionModel.dblclick()');
4648 NETDATA.resetAllCharts(state);
4650 wheel: function(event, dygraph, context) {
4653 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4654 state.log('interactionModel.wheel()');
4656 // Take the offset of a mouse event on the dygraph canvas and
4657 // convert it to a pair of percentages from the bottom left.
4658 // (Not top left, bottom is where the lower value is.)
4659 function offsetToPercentage(g, offsetX, offsetY) {
4660 // This is calculating the pixel offset of the leftmost date.
4661 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4662 var yar0 = g.yAxisRange(0);
4664 // This is calculating the pixel of the highest value. (Top pixel)
4665 var yOffset = g.toDomCoords(null, yar0[1])[1];
4667 // x y w and h are relative to the corner of the drawing area,
4668 // so that the upper corner of the drawing area is (0, 0).
4669 var x = offsetX - xOffset;
4670 var y = offsetY - yOffset;
4672 // This is computing the rightmost pixel, effectively defining the
4674 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4676 // This is computing the lowest pixel, effectively defining the height.
4677 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4679 // Percentage from the left.
4680 var xPct = w === 0 ? 0 : (x / w);
4681 // Percentage from the top.
4682 var yPct = h === 0 ? 0 : (y / h);
4684 // The (1-) part below changes it from "% distance down from the top"
4685 // to "% distance up from the bottom".
4686 return [xPct, (1-yPct)];
4689 // Adjusts [x, y] toward each other by zoomInPercentage%
4690 // Split it so the left/bottom axis gets xBias/yBias of that change and
4691 // tight/top gets (1-xBias)/(1-yBias) of that change.
4693 // If a bias is missing it splits it down the middle.
4694 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4695 xBias = xBias || 0.5;
4696 yBias = yBias || 0.5;
4698 function adjustAxis(axis, zoomInPercentage, bias) {
4699 var delta = axis[1] - axis[0];
4700 var increment = delta * zoomInPercentage;
4701 var foo = [increment * bias, increment * (1-bias)];
4703 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4706 var yAxes = g.yAxisRanges();
4708 for (var i = 0; i < yAxes.length; i++) {
4709 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4712 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4715 if(event.altKey || event.shiftKey) {
4716 state.dygraph_user_action = true;
4718 state.globalSelectionSyncStop();
4719 state.globalSelectionSyncDelay();
4721 // http://dygraphs.com/gallery/interaction-api.js
4723 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4725 normal_def = event.wheelDelta / 40;
4728 normal_def = event.deltaY * -1.2;
4730 var normal = (event.detail) ? event.detail * -1 : normal_def;
4731 var percentage = normal / 50;
4733 if (!(event.offsetX && event.offsetY)){
4734 event.offsetX = event.layerX - event.target.offsetLeft;
4735 event.offsetY = event.layerY - event.target.offsetTop;
4738 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4739 var xPct = percentages[0];
4740 var yPct = percentages[1];
4742 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4743 var after = new_x_range[0];
4744 var before = new_x_range[1];
4746 var first = state.netdata_first + state.data_update_every;
4747 var last = state.netdata_last + state.data_update_every;
4750 after -= (before - last);
4757 state.setMode('zoom');
4758 if(state.updateChartPanOrZoom(after, before) === true)
4759 dygraph.updateOptions({ dateWindow: [ after, before ] });
4761 event.preventDefault();
4764 touchstart: function(event, dygraph, context) {
4765 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4766 state.log('interactionModel.touchstart()');
4768 state.dygraph_user_action = true;
4769 state.setMode('zoom');
4772 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4774 // we overwrite the touch directions at the end, to overwrite
4775 // the internal default of dygraph
4776 context.touchDirections = { x: true, y: false };
4778 state.dygraph_last_touch_start = Date.now();
4779 state.dygraph_last_touch_move = 0;
4781 if(typeof event.touches[0].pageX === 'number')
4782 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4784 state.dygraph_last_touch_page_x = 0;
4786 touchmove: function(event, dygraph, context) {
4787 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4788 state.log('interactionModel.touchmove()');
4790 state.dygraph_user_action = true;
4791 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4793 state.dygraph_last_touch_move = Date.now();
4795 touchend: function(event, dygraph, context) {
4796 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4797 state.log('interactionModel.touchend()');
4799 state.dygraph_user_action = true;
4800 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4802 // if it didn't move, it is a selection
4803 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4804 // internal api of dygraph
4805 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4806 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4807 if(NETDATA.dygraphSetSelection(state, t) === true)
4808 state.globalSelectionSync(t);
4811 // if it was double tap within double click time, reset the charts
4812 var now = Date.now();
4813 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4814 if(state.dygraph_last_touch_move === 0) {
4815 var dt = now - state.dygraph_last_touch_end;
4816 if(dt <= NETDATA.options.current.double_click_speed)
4817 NETDATA.resetAllCharts(state);
4821 // remember the timestamp of the last touch end
4822 state.dygraph_last_touch_end = now;
4827 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4828 state.dygraph_options.drawGrid = false;
4829 state.dygraph_options.drawAxis = false;
4830 state.dygraph_options.title = undefined;
4831 state.dygraph_options.ylabel = undefined;
4832 state.dygraph_options.yLabelWidth = 0;
4833 state.dygraph_options.labelsDivWidth = 120;
4834 state.dygraph_options.labelsDivStyles.width = '120px';
4835 state.dygraph_options.labelsSeparateLines = true;
4836 state.dygraph_options.rightGap = 0;
4837 state.dygraph_options.yRangePad = 1;
4840 if(smooth === true) {
4841 state.dygraph_smooth_eligible = true;
4843 if(NETDATA.options.current.smooth_plot === true)
4844 state.dygraph_options.plotter = smoothPlotter;
4846 else state.dygraph_smooth_eligible = false;
4848 state.dygraph_instance = new Dygraph(state.element_chart,
4849 data.result.data, state.dygraph_options);
4851 state.dygraph_force_zoom = false;
4852 state.dygraph_user_action = false;
4853 state.dygraph_last_rendered = Date.now();
4855 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4856 state.__commonMin = self.data('common-min') || null;
4857 state.__commonMax = self.data('common-max') || null;
4860 state.log('incompatible version of Dygraph detected');
4861 state.__commonMin = null;
4862 state.__commonMax = null;
4868 // ----------------------------------------------------------------------------------------------------------------
4871 NETDATA.morrisInitialize = function(callback) {
4872 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4874 // morris requires raphael
4875 if(!NETDATA.chartLibraries.raphael.initialized) {
4876 if(NETDATA.chartLibraries.raphael.enabled) {
4877 NETDATA.raphaelInitialize(function() {
4878 NETDATA.morrisInitialize(callback);
4882 NETDATA.chartLibraries.morris.enabled = false;
4883 if(typeof callback === "function")
4888 NETDATA._loadCSS(NETDATA.morris_css);
4891 url: NETDATA.morris_js,
4894 xhrFields: { withCredentials: true } // required for the cookie
4897 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4900 NETDATA.chartLibraries.morris.enabled = false;
4901 NETDATA.error(100, NETDATA.morris_js);
4903 .always(function() {
4904 if(typeof callback === "function")
4910 NETDATA.chartLibraries.morris.enabled = false;
4911 if(typeof callback === "function")
4916 NETDATA.morrisChartUpdate = function(state, data) {
4917 state.morris_instance.setData(data.result.data);
4921 NETDATA.morrisChartCreate = function(state, data) {
4923 state.morris_options = {
4924 element: state.element_chart.id,
4925 data: data.result.data,
4927 ykeys: data.dimension_names,
4928 labels: data.dimension_names,
4934 continuousLine: false,
4935 behaveLikeLine: false
4938 if(state.chart.chart_type === 'line')
4939 state.morris_instance = new Morris.Line(state.morris_options);
4941 else if(state.chart.chart_type === 'area') {
4942 state.morris_options.behaveLikeLine = true;
4943 state.morris_instance = new Morris.Area(state.morris_options);
4946 state.morris_instance = new Morris.Area(state.morris_options);
4951 // ----------------------------------------------------------------------------------------------------------------
4954 NETDATA.raphaelInitialize = function(callback) {
4955 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4957 url: NETDATA.raphael_js,
4960 xhrFields: { withCredentials: true } // required for the cookie
4963 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4966 NETDATA.chartLibraries.raphael.enabled = false;
4967 NETDATA.error(100, NETDATA.raphael_js);
4969 .always(function() {
4970 if(typeof callback === "function")
4975 NETDATA.chartLibraries.raphael.enabled = false;
4976 if(typeof callback === "function")
4981 NETDATA.raphaelChartUpdate = function(state, data) {
4982 $(state.element_chart).raphael(data.result, {
4983 width: state.chartWidth(),
4984 height: state.chartHeight()
4990 NETDATA.raphaelChartCreate = function(state, data) {
4991 $(state.element_chart).raphael(data.result, {
4992 width: state.chartWidth(),
4993 height: state.chartHeight()
4999 // ----------------------------------------------------------------------------------------------------------------
5002 NETDATA.c3Initialize = function(callback) {
5003 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
5006 if(!NETDATA.chartLibraries.d3.initialized) {
5007 if(NETDATA.chartLibraries.d3.enabled) {
5008 NETDATA.d3Initialize(function() {
5009 NETDATA.c3Initialize(callback);
5013 NETDATA.chartLibraries.c3.enabled = false;
5014 if(typeof callback === "function")
5019 NETDATA._loadCSS(NETDATA.c3_css);
5025 xhrFields: { withCredentials: true } // required for the cookie
5028 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
5031 NETDATA.chartLibraries.c3.enabled = false;
5032 NETDATA.error(100, NETDATA.c3_js);
5034 .always(function() {
5035 if(typeof callback === "function")
5041 NETDATA.chartLibraries.c3.enabled = false;
5042 if(typeof callback === "function")
5047 NETDATA.c3ChartUpdate = function(state, data) {
5048 state.c3_instance.destroy();
5049 return NETDATA.c3ChartCreate(state, data);
5051 //state.c3_instance.load({
5052 // rows: data.result,
5059 NETDATA.c3ChartCreate = function(state, data) {
5061 state.element_chart.id = 'c3-' + state.uuid;
5062 // console.log('id = ' + state.element_chart.id);
5064 state.c3_instance = c3.generate({
5065 bindto: '#' + state.element_chart.id,
5067 width: state.chartWidth(),
5068 height: state.chartHeight()
5071 pattern: state.chartColors()
5076 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5082 format: function(x) {
5083 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5110 // console.log(state.c3_instance);
5115 // ----------------------------------------------------------------------------------------------------------------
5118 NETDATA.d3Initialize = function(callback) {
5119 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5124 xhrFields: { withCredentials: true } // required for the cookie
5127 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5130 NETDATA.chartLibraries.d3.enabled = false;
5131 NETDATA.error(100, NETDATA.d3_js);
5133 .always(function() {
5134 if(typeof callback === "function")
5139 NETDATA.chartLibraries.d3.enabled = false;
5140 if(typeof callback === "function")
5145 NETDATA.d3ChartUpdate = function(state, data) {
5152 NETDATA.d3ChartCreate = function(state, data) {
5159 // ----------------------------------------------------------------------------------------------------------------
5162 NETDATA.googleInitialize = function(callback) {
5163 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5165 url: NETDATA.google_js,
5168 xhrFields: { withCredentials: true } // required for the cookie
5171 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5172 google.load('visualization', '1.1', {
5173 'packages': ['corechart', 'controls'],
5174 'callback': callback
5178 NETDATA.chartLibraries.google.enabled = false;
5179 NETDATA.error(100, NETDATA.google_js);
5180 if(typeof callback === "function")
5185 NETDATA.chartLibraries.google.enabled = false;
5186 if(typeof callback === "function")
5191 NETDATA.googleChartUpdate = function(state, data) {
5192 var datatable = new google.visualization.DataTable(data.result);
5193 state.google_instance.draw(datatable, state.google_options);
5197 NETDATA.googleChartCreate = function(state, data) {
5198 var datatable = new google.visualization.DataTable(data.result);
5200 state.google_options = {
5201 colors: state.chartColors(),
5203 // do not set width, height - the chart resizes itself
5204 //width: state.chartWidth(),
5205 //height: state.chartHeight(),
5210 // title: "Time of Day",
5211 // format:'HH:mm:ss',
5212 viewWindowMode: 'maximized',
5224 viewWindowMode: 'pretty',
5239 focusTarget: 'category',
5246 titlePosition: 'out',
5257 curveType: 'function',
5262 switch(state.chart.chart_type) {
5264 state.google_options.vAxis.viewWindowMode = 'maximized';
5265 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5266 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5270 state.google_options.isStacked = true;
5271 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5272 state.google_options.vAxis.viewWindowMode = 'maximized';
5273 state.google_options.vAxis.minValue = null;
5274 state.google_options.vAxis.maxValue = null;
5275 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5280 state.google_options.lineWidth = 2;
5281 state.google_instance = new google.visualization.LineChart(state.element_chart);
5285 state.google_instance.draw(datatable, state.google_options);
5289 // ----------------------------------------------------------------------------------------------------------------
5291 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5292 if(typeof value !== 'number') value = 0;
5293 if(typeof min !== 'number') min = 0;
5294 if(typeof max !== 'number') max = 0;
5296 if(min > value) min = value;
5297 if(max < value) max = value;
5299 // make sure it is zero based
5300 if(min > 0) min = 0;
5301 if(max < 0) max = 0;
5306 pcent = Math.round(value * 100 / max);
5307 if(pcent === 0) pcent = 0.1;
5311 pcent = Math.round(-value * 100 / min);
5312 if(pcent === 0) pcent = -0.1;
5318 // ----------------------------------------------------------------------------------------------------------------
5321 NETDATA.easypiechartInitialize = function(callback) {
5322 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5324 url: NETDATA.easypiechart_js,
5327 xhrFields: { withCredentials: true } // required for the cookie
5330 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5333 NETDATA.chartLibraries.easypiechart.enabled = false;
5334 NETDATA.error(100, NETDATA.easypiechart_js);
5336 .always(function() {
5337 if(typeof callback === "function")
5342 NETDATA.chartLibraries.easypiechart.enabled = false;
5343 if(typeof callback === "function")
5348 NETDATA.easypiechartClearSelection = function(state) {
5349 if(typeof state.easyPieChartEvent !== 'undefined') {
5350 if(state.easyPieChartEvent.timer !== undefined) {
5351 clearTimeout(state.easyPieChartEvent.timer);
5354 state.easyPieChartEvent.timer = undefined;
5357 if(state.isAutoRefreshable() === true && state.data !== null) {
5358 NETDATA.easypiechartChartUpdate(state, state.data);
5361 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5362 state.easyPieChart_instance.update(0);
5364 state.easyPieChart_instance.enableAnimation();
5369 NETDATA.easypiechartSetSelection = function(state, t) {
5370 if(state.timeIsVisible(t) !== true)
5371 return NETDATA.easypiechartClearSelection(state);
5373 var slot = state.calculateRowForTime(t);
5374 if(slot < 0 || slot >= state.data.result.length)
5375 return NETDATA.easypiechartClearSelection(state);
5377 if(typeof state.easyPieChartEvent === 'undefined') {
5378 state.easyPieChartEvent = {
5385 var value = state.data.result[state.data.result.length - 1 - slot];
5386 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5387 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5388 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5390 state.easyPieChartEvent.value = value;
5391 state.easyPieChartEvent.pcent = pcent;
5392 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5394 if(state.easyPieChartEvent.timer === undefined) {
5395 state.easyPieChart_instance.disableAnimation();
5397 state.easyPieChartEvent.timer = setTimeout(function() {
5398 state.easyPieChartEvent.timer = undefined;
5399 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5400 }, NETDATA.options.current.charts_selection_animation_delay);
5406 NETDATA.easypiechartChartUpdate = function(state, data) {
5407 var value, min, max, pcent;
5409 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5414 value = data.result[0];
5415 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5416 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5417 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5420 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5421 state.easyPieChart_instance.update(pcent);
5425 NETDATA.easypiechartChartCreate = function(state, data) {
5426 var self = $(state.element);
5427 var chart = $(state.element_chart);
5429 var value = data.result[0];
5430 var min = self.data('easypiechart-min-value') || null;
5431 var max = self.data('easypiechart-max-value') || null;
5432 var adjust = self.data('easypiechart-adjust') || null;
5435 min = NETDATA.commonMin.get(state);
5436 state.easyPieChartMin = null;
5439 state.easyPieChartMin = min;
5442 max = NETDATA.commonMax.get(state);
5443 state.easyPieChartMax = null;
5446 state.easyPieChartMax = max;
5448 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5450 chart.data('data-percent', pcent);
5454 case 'width': size = state.chartHeight(); break;
5455 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5456 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5458 default: size = state.chartWidth(); break;
5460 state.element.style.width = size + 'px';
5461 state.element.style.height = size + 'px';
5463 var stroke = Math.floor(size / 22);
5464 if(stroke < 3) stroke = 2;
5466 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5467 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5468 state.easyPieChartLabel = document.createElement('span');
5469 state.easyPieChartLabel.className = 'easyPieChartLabel';
5470 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5471 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5472 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5473 state.element_chart.appendChild(state.easyPieChartLabel);
5475 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5476 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5477 state.easyPieChartTitle = document.createElement('span');
5478 state.easyPieChartTitle.className = 'easyPieChartTitle';
5479 state.easyPieChartTitle.innerText = state.title;
5480 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5481 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5482 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5483 state.element_chart.appendChild(state.easyPieChartTitle);
5485 var unitfontsize = Math.round(titlefontsize * 0.9);
5486 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5487 state.easyPieChartUnits = document.createElement('span');
5488 state.easyPieChartUnits.className = 'easyPieChartUnits';
5489 state.easyPieChartUnits.innerText = state.units;
5490 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5491 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5492 state.element_chart.appendChild(state.easyPieChartUnits);
5494 var barColor = self.data('easypiechart-barcolor');
5495 if(typeof barColor === 'undefined' || barColor === null)
5496 barColor = state.chartColors()[0];
5498 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5499 var tmp = eval(barColor);
5500 if(typeof tmp === 'function')
5504 chart.easyPieChart({
5506 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5507 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5508 scaleLength: self.data('easypiechart-scalelength') || 5,
5509 lineCap: self.data('easypiechart-linecap') || 'round',
5510 lineWidth: self.data('easypiechart-linewidth') || stroke,
5511 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5512 size: self.data('easypiechart-size') || size,
5513 rotate: self.data('easypiechart-rotate') || 0,
5514 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5515 easing: self.data('easypiechart-easing') || undefined
5518 // when we just re-create the chart
5519 // do not animate the first update
5521 if(typeof state.easyPieChart_instance !== 'undefined')
5524 state.easyPieChart_instance = chart.data('easyPieChart');
5525 if(animate === false) state.easyPieChart_instance.disableAnimation();
5526 state.easyPieChart_instance.update(pcent);
5527 if(animate === false) state.easyPieChart_instance.enableAnimation();
5531 // ----------------------------------------------------------------------------------------------------------------
5534 NETDATA.gaugeInitialize = function(callback) {
5535 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5537 url: NETDATA.gauge_js,
5540 xhrFields: { withCredentials: true } // required for the cookie
5543 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5546 NETDATA.chartLibraries.gauge.enabled = false;
5547 NETDATA.error(100, NETDATA.gauge_js);
5549 .always(function() {
5550 if(typeof callback === "function")
5555 NETDATA.chartLibraries.gauge.enabled = false;
5556 if(typeof callback === "function")
5561 NETDATA.gaugeAnimation = function(state, status) {
5564 if(typeof status === 'boolean' && status === false)
5566 else if(typeof status === 'number')
5569 // console.log('gauge speed ' + speed);
5570 state.gauge_instance.animationSpeed = speed;
5571 state.___gaugeOld__.speed = speed;
5574 NETDATA.gaugeSet = function(state, value, min, max) {
5575 if(typeof value !== 'number') value = 0;
5576 if(typeof min !== 'number') min = 0;
5577 if(typeof max !== 'number') max = 0;
5578 if(value > max) max = value;
5579 if(value < min) min = value;
5585 else if(min === max)
5588 // gauge.js has an issue if the needle
5589 // is smaller than min or larger than max
5590 // when we set the new values
5591 // the needle will go crazy
5593 // to prevent it, we always feed it
5594 // with a percentage, so that the needle
5595 // is always between min and max
5596 var pcent = (value - min) * 100 / (max - min);
5598 // bug fix for gauge.js 1.3.1
5599 // if the value is the absolute min or max, the chart is broken
5600 if(pcent < 0.001) pcent = 0.001;
5601 if(pcent > 99.999) pcent = 99.999;
5603 state.gauge_instance.set(pcent);
5604 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5606 state.___gaugeOld__.value = value;
5607 state.___gaugeOld__.min = min;
5608 state.___gaugeOld__.max = max;
5611 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5612 if(state.___gaugeOld__.valueLabel !== value) {
5613 state.___gaugeOld__.valueLabel = value;
5614 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5616 if(state.___gaugeOld__.minLabel !== min) {
5617 state.___gaugeOld__.minLabel = min;
5618 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5620 if(state.___gaugeOld__.maxLabel !== max) {
5621 state.___gaugeOld__.maxLabel = max;
5622 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5626 NETDATA.gaugeClearSelection = function(state) {
5627 if(typeof state.gaugeEvent !== 'undefined') {
5628 if(state.gaugeEvent.timer !== undefined) {
5629 clearTimeout(state.gaugeEvent.timer);
5632 state.gaugeEvent.timer = undefined;
5635 if(state.isAutoRefreshable() === true && state.data !== null) {
5636 NETDATA.gaugeChartUpdate(state, state.data);
5639 NETDATA.gaugeAnimation(state, false);
5640 NETDATA.gaugeSet(state, null, null, null);
5641 NETDATA.gaugeSetLabels(state, null, null, null);
5644 NETDATA.gaugeAnimation(state, true);
5648 NETDATA.gaugeSetSelection = function(state, t) {
5649 if(state.timeIsVisible(t) !== true)
5650 return NETDATA.gaugeClearSelection(state);
5652 var slot = state.calculateRowForTime(t);
5653 if(slot < 0 || slot >= state.data.result.length)
5654 return NETDATA.gaugeClearSelection(state);
5656 if(typeof state.gaugeEvent === 'undefined') {
5657 state.gaugeEvent = {
5665 var value = state.data.result[state.data.result.length - 1 - slot];
5666 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5667 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5669 // make sure it is zero based
5670 if(min > 0) min = 0;
5671 if(max < 0) max = 0;
5673 state.gaugeEvent.value = value;
5674 state.gaugeEvent.min = min;
5675 state.gaugeEvent.max = max;
5676 NETDATA.gaugeSetLabels(state, value, min, max);
5678 if(state.gaugeEvent.timer === undefined) {
5679 NETDATA.gaugeAnimation(state, false);
5681 state.gaugeEvent.timer = setTimeout(function() {
5682 state.gaugeEvent.timer = undefined;
5683 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5684 }, NETDATA.options.current.charts_selection_animation_delay);
5690 NETDATA.gaugeChartUpdate = function(state, data) {
5691 var value, min, max;
5693 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5697 NETDATA.gaugeSetLabels(state, null, null, null);
5700 value = data.result[0];
5701 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5702 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5703 if(value < min) min = value;
5704 if(value > max) max = value;
5706 // make sure it is zero based
5707 if(min > 0) min = 0;
5708 if(max < 0) max = 0;
5710 NETDATA.gaugeSetLabels(state, value, min, max);
5713 NETDATA.gaugeSet(state, value, min, max);
5717 NETDATA.gaugeChartCreate = function(state, data) {
5718 var self = $(state.element);
5719 // var chart = $(state.element_chart);
5721 var value = data.result[0];
5722 var min = self.data('gauge-min-value') || null;
5723 var max = self.data('gauge-max-value') || null;
5724 var adjust = self.data('gauge-adjust') || null;
5725 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5726 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5727 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5728 var stopColor = self.data('gauge-stop-color') || void 0;
5729 var generateGradient = self.data('gauge-generate-gradient') || false;
5732 min = NETDATA.commonMin.get(state);
5733 state.gaugeMin = null;
5736 state.gaugeMin = min;
5739 max = NETDATA.commonMax.get(state);
5740 state.gaugeMax = null;
5743 state.gaugeMax = max;
5745 // make sure it is zero based
5746 if(min > 0) min = 0;
5747 if(max < 0) max = 0;
5749 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5751 // case 'width': width = height * ratio; break;
5753 // default: height = width / ratio; break;
5755 //state.element.style.width = width.toString() + 'px';
5756 //state.element.style.height = height.toString() + 'px';
5761 lines: 12, // The number of lines to draw
5762 angle: 0.15, // The span of the gauge arc
5763 lineWidth: 0.50, // The line thickness
5764 radiusScale: 0.85, // Relative radius
5766 length: 0.8, // 0.9 The radius of the inner circle
5767 strokeWidth: 0.035, // The rotation offset
5768 color: pointerColor // Fill color
5770 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5771 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5772 colorStart: startColor, // Colors
5773 colorStop: stopColor, // just experiment with them
5774 strokeColor: strokeColor, // to see which ones work best for you
5775 generateGradient: (generateGradient === true),
5777 highDpiSupport: true // High resolution support
5780 if (generateGradient.constructor === Array) {
5782 // data-gauge-generate-gradient="[0, 50, 100]"
5783 // data-gauge-gradient-percent-color-0="#FFFFFF"
5784 // data-gauge-gradient-percent-color-50="#999900"
5785 // data-gauge-gradient-percent-color-100="#000000"
5787 options.percentColors = [];
5788 var len = generateGradient.length;
5790 var pcent = generateGradient[len];
5791 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5792 if(color !== false) {
5796 options.percentColors.unshift(a);
5799 if(options.percentColors.length === 0)
5800 delete options.percentColors;
5802 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5803 //noinspection PointlessArithmeticExpressionJS
5804 options.percentColors = [
5805 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5806 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5807 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5808 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5809 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5810 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5811 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5812 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5813 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5814 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5815 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5818 state.gauge_canvas = document.createElement('canvas');
5819 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5820 state.gauge_canvas.className = 'gaugeChart';
5821 state.gauge_canvas.width = width;
5822 state.gauge_canvas.height = height;
5823 state.element_chart.appendChild(state.gauge_canvas);
5825 var valuefontsize = Math.floor(height / 6);
5826 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5827 state.gaugeChartLabel = document.createElement('span');
5828 state.gaugeChartLabel.className = 'gaugeChartLabel';
5829 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5830 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5831 state.element_chart.appendChild(state.gaugeChartLabel);
5833 var titlefontsize = Math.round(valuefontsize / 2);
5835 state.gaugeChartTitle = document.createElement('span');
5836 state.gaugeChartTitle.className = 'gaugeChartTitle';
5837 state.gaugeChartTitle.innerText = state.title;
5838 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5839 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5840 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5841 state.element_chart.appendChild(state.gaugeChartTitle);
5843 var unitfontsize = Math.round(titlefontsize * 0.9);
5844 state.gaugeChartUnits = document.createElement('span');
5845 state.gaugeChartUnits.className = 'gaugeChartUnits';
5846 state.gaugeChartUnits.innerText = state.units;
5847 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5848 state.element_chart.appendChild(state.gaugeChartUnits);
5850 state.gaugeChartMin = document.createElement('span');
5851 state.gaugeChartMin.className = 'gaugeChartMin';
5852 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5853 state.element_chart.appendChild(state.gaugeChartMin);
5855 state.gaugeChartMax = document.createElement('span');
5856 state.gaugeChartMax.className = 'gaugeChartMax';
5857 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5858 state.element_chart.appendChild(state.gaugeChartMax);
5860 // when we just re-create the chart
5861 // do not animate the first update
5863 if(typeof state.gauge_instance !== 'undefined')
5866 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5868 state.___gaugeOld__ = {
5877 // we will always feed a percentage
5878 state.gauge_instance.minValue = 0;
5879 state.gauge_instance.maxValue = 100;
5881 NETDATA.gaugeAnimation(state, animate);
5882 NETDATA.gaugeSet(state, value, min, max);
5883 NETDATA.gaugeSetLabels(state, value, min, max);
5884 NETDATA.gaugeAnimation(state, true);
5888 // ----------------------------------------------------------------------------------------------------------------
5889 // Charts Libraries Registration
5891 NETDATA.chartLibraries = {
5893 initialize: NETDATA.dygraphInitialize,
5894 create: NETDATA.dygraphChartCreate,
5895 update: NETDATA.dygraphChartUpdate,
5896 resize: function(state) {
5897 if(typeof state.dygraph_instance.resize === 'function')
5898 state.dygraph_instance.resize();
5900 setSelection: NETDATA.dygraphSetSelection,
5901 clearSelection: NETDATA.dygraphClearSelection,
5902 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5905 format: function(state) { void(state); return 'json'; },
5906 options: function(state) { void(state); return 'ms|flip'; },
5907 legend: function(state) {
5908 return (this.isSparkline(state) === false)?'right-side':null;
5910 autoresize: function(state) { void(state); return true; },
5911 max_updates_to_recreate: function(state) { void(state); return 5000; },
5912 track_colors: function(state) { void(state); return true; },
5913 pixels_per_point: function(state) {
5914 return (this.isSparkline(state) === false)?3:2;
5916 isSparkline: function(state) {
5917 if(typeof state.dygraph_sparkline === 'undefined') {
5918 var t = $(state.element).data('dygraph-theme');
5919 state.dygraph_sparkline = (t === 'sparkline');
5921 return state.dygraph_sparkline;
5925 initialize: NETDATA.sparklineInitialize,
5926 create: NETDATA.sparklineChartCreate,
5927 update: NETDATA.sparklineChartUpdate,
5929 setSelection: undefined, // function(state, t) { void(state); return true; },
5930 clearSelection: undefined, // function(state) { void(state); return true; },
5931 toolboxPanAndZoom: null,
5934 format: function(state) { void(state); return 'array'; },
5935 options: function(state) { void(state); return 'flip|abs'; },
5936 legend: function(state) { void(state); return null; },
5937 autoresize: function(state) { void(state); return false; },
5938 max_updates_to_recreate: function(state) { void(state); return 5000; },
5939 track_colors: function(state) { void(state); return false; },
5940 pixels_per_point: function(state) { void(state); return 3; }
5943 initialize: NETDATA.peityInitialize,
5944 create: NETDATA.peityChartCreate,
5945 update: NETDATA.peityChartUpdate,
5947 setSelection: undefined, // function(state, t) { void(state); return true; },
5948 clearSelection: undefined, // function(state) { void(state); return true; },
5949 toolboxPanAndZoom: null,
5952 format: function(state) { void(state); return 'ssvcomma'; },
5953 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5954 legend: function(state) { void(state); return null; },
5955 autoresize: function(state) { void(state); return false; },
5956 max_updates_to_recreate: function(state) { void(state); return 5000; },
5957 track_colors: function(state) { void(state); return false; },
5958 pixels_per_point: function(state) { void(state); return 3; }
5961 initialize: NETDATA.morrisInitialize,
5962 create: NETDATA.morrisChartCreate,
5963 update: NETDATA.morrisChartUpdate,
5965 setSelection: undefined, // function(state, t) { void(state); return true; },
5966 clearSelection: undefined, // function(state) { void(state); return true; },
5967 toolboxPanAndZoom: null,
5970 format: function(state) { void(state); return 'json'; },
5971 options: function(state) { void(state); return 'objectrows|ms'; },
5972 legend: function(state) { void(state); return null; },
5973 autoresize: function(state) { void(state); return false; },
5974 max_updates_to_recreate: function(state) { void(state); return 50; },
5975 track_colors: function(state) { void(state); return false; },
5976 pixels_per_point: function(state) { void(state); return 15; }
5979 initialize: NETDATA.googleInitialize,
5980 create: NETDATA.googleChartCreate,
5981 update: NETDATA.googleChartUpdate,
5983 setSelection: undefined, //function(state, t) { void(state); return true; },
5984 clearSelection: undefined, //function(state) { void(state); return true; },
5985 toolboxPanAndZoom: null,
5988 format: function(state) { void(state); return 'datatable'; },
5989 options: function(state) { void(state); return ''; },
5990 legend: function(state) { void(state); return null; },
5991 autoresize: function(state) { void(state); return false; },
5992 max_updates_to_recreate: function(state) { void(state); return 300; },
5993 track_colors: function(state) { void(state); return false; },
5994 pixels_per_point: function(state) { void(state); return 4; }
5997 initialize: NETDATA.raphaelInitialize,
5998 create: NETDATA.raphaelChartCreate,
5999 update: NETDATA.raphaelChartUpdate,
6001 setSelection: undefined, // function(state, t) { void(state); return true; },
6002 clearSelection: undefined, // function(state) { void(state); return true; },
6003 toolboxPanAndZoom: null,
6006 format: function(state) { void(state); return 'json'; },
6007 options: function(state) { void(state); return ''; },
6008 legend: function(state) { void(state); return null; },
6009 autoresize: function(state) { void(state); return false; },
6010 max_updates_to_recreate: function(state) { void(state); return 5000; },
6011 track_colors: function(state) { void(state); return false; },
6012 pixels_per_point: function(state) { void(state); return 3; }
6015 initialize: NETDATA.c3Initialize,
6016 create: NETDATA.c3ChartCreate,
6017 update: NETDATA.c3ChartUpdate,
6019 setSelection: undefined, // function(state, t) { void(state); return true; },
6020 clearSelection: undefined, // function(state) { void(state); return true; },
6021 toolboxPanAndZoom: null,
6024 format: function(state) { void(state); return 'csvjsonarray'; },
6025 options: function(state) { void(state); return 'milliseconds'; },
6026 legend: function(state) { void(state); return null; },
6027 autoresize: function(state) { void(state); return false; },
6028 max_updates_to_recreate: function(state) { void(state); return 5000; },
6029 track_colors: function(state) { void(state); return false; },
6030 pixels_per_point: function(state) { void(state); return 15; }
6033 initialize: NETDATA.d3Initialize,
6034 create: NETDATA.d3ChartCreate,
6035 update: NETDATA.d3ChartUpdate,
6037 setSelection: undefined, // function(state, t) { void(state); return true; },
6038 clearSelection: undefined, // function(state) { void(state); return true; },
6039 toolboxPanAndZoom: null,
6042 format: function(state) { void(state); return 'json'; },
6043 options: function(state) { void(state); return ''; },
6044 legend: function(state) { void(state); return null; },
6045 autoresize: function(state) { void(state); return false; },
6046 max_updates_to_recreate: function(state) { void(state); return 5000; },
6047 track_colors: function(state) { void(state); return false; },
6048 pixels_per_point: function(state) { void(state); return 3; }
6051 initialize: NETDATA.easypiechartInitialize,
6052 create: NETDATA.easypiechartChartCreate,
6053 update: NETDATA.easypiechartChartUpdate,
6055 setSelection: NETDATA.easypiechartSetSelection,
6056 clearSelection: NETDATA.easypiechartClearSelection,
6057 toolboxPanAndZoom: null,
6060 format: function(state) { void(state); return 'array'; },
6061 options: function(state) { void(state); return 'absolute'; },
6062 legend: function(state) { void(state); return null; },
6063 autoresize: function(state) { void(state); return false; },
6064 max_updates_to_recreate: function(state) { void(state); return 5000; },
6065 track_colors: function(state) { void(state); return true; },
6066 pixels_per_point: function(state) { void(state); return 3; },
6070 initialize: NETDATA.gaugeInitialize,
6071 create: NETDATA.gaugeChartCreate,
6072 update: NETDATA.gaugeChartUpdate,
6074 setSelection: NETDATA.gaugeSetSelection,
6075 clearSelection: NETDATA.gaugeClearSelection,
6076 toolboxPanAndZoom: null,
6079 format: function(state) { void(state); return 'array'; },
6080 options: function(state) { void(state); return 'absolute'; },
6081 legend: function(state) { void(state); return null; },
6082 autoresize: function(state) { void(state); return false; },
6083 max_updates_to_recreate: function(state) { void(state); return 5000; },
6084 track_colors: function(state) { void(state); return true; },
6085 pixels_per_point: function(state) { void(state); return 3; },
6090 NETDATA.registerChartLibrary = function(library, url) {
6091 if(NETDATA.options.debug.libraries === true)
6092 console.log("registering chart library: " + library);
6094 NETDATA.chartLibraries[library].url = url;
6095 NETDATA.chartLibraries[library].initialized = true;
6096 NETDATA.chartLibraries[library].enabled = true;
6099 // ----------------------------------------------------------------------------------------------------------------
6100 // Load required JS libraries and CSS
6102 NETDATA.requiredJs = [
6104 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6106 isAlreadyLoaded: function() {
6107 // check if bootstrap is loaded
6108 if(typeof $().emulateTransitionEnd === 'function')
6111 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6116 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6117 isAlreadyLoaded: function() { return false; }
6121 NETDATA.requiredCSS = [
6123 url: NETDATA.themes.current.bootstrap_css,
6124 isAlreadyLoaded: function() {
6125 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6129 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6130 isAlreadyLoaded: function() { return false; }
6133 url: NETDATA.themes.current.dashboard_css,
6134 isAlreadyLoaded: function() { return false; }
6138 NETDATA.loadedRequiredJs = 0;
6139 NETDATA.loadRequiredJs = function(index, callback) {
6140 if(index >= NETDATA.requiredJs.length) {
6141 if(typeof callback === 'function')
6146 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6147 NETDATA.loadedRequiredJs++;
6148 NETDATA.loadRequiredJs(++index, callback);
6152 if(NETDATA.options.debug.main_loop === true)
6153 console.log('loading ' + NETDATA.requiredJs[index].url);
6156 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6160 url: NETDATA.requiredJs[index].url,
6163 xhrFields: { withCredentials: true } // required for the cookie
6166 if(NETDATA.options.debug.main_loop === true)
6167 console.log('loaded ' + NETDATA.requiredJs[index].url);
6170 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6172 .always(function() {
6173 NETDATA.loadedRequiredJs++;
6176 NETDATA.loadRequiredJs(++index, callback);
6180 NETDATA.loadRequiredJs(++index, callback);
6183 NETDATA.loadRequiredCSS = function(index) {
6184 if(index >= NETDATA.requiredCSS.length)
6187 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6188 NETDATA.loadRequiredCSS(++index);
6192 if(NETDATA.options.debug.main_loop === true)
6193 console.log('loading ' + NETDATA.requiredCSS[index].url);
6195 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6196 NETDATA.loadRequiredCSS(++index);
6200 // ----------------------------------------------------------------------------------------------------------------
6201 // Registry of netdata hosts
6204 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6205 chart_div_offset: 100, // give that space above the chart when scrolling to it
6206 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6207 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6209 ms_penalty: 0, // the time penalty of the next alarm
6210 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6211 // if alarms are shown faster than: one per 500ms
6213 notifications: false, // when true, the browser supports notifications (may not be granted though)
6214 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6215 first_notification_id: 0, // the id of the first alarm_log entry for this session
6216 // this is used to prevent CLEAR notifications for past events
6217 // notifications_shown: [],
6219 server: null, // the server to connect to for fetching alarms
6220 current: null, // the list of raised alarms - updated in the background
6221 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6223 notify: function(entry) {
6224 // console.log('alarm ' + entry.unique_id);
6226 if(entry.updated === true) {
6227 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6231 var value_string = entry.value_string;
6233 if(NETDATA.alarms.current !== null) {
6234 // get the current value_string
6235 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6236 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6237 value_string = t.value_string;
6240 var name = entry.name.replace(/_/g, ' ');
6241 var status = entry.status.toLowerCase();
6242 var title = name + ' = ' + value_string.toString();
6243 var tag = entry.alarm_id;
6244 var icon = 'images/seo-performance-128.png';
6245 var interaction = false;
6249 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6251 switch(entry.status) {
6259 case 'UNINITIALIZED':
6263 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6264 // console.log('alarm ' + entry.unique_id + ' is not current');
6267 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6268 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6271 if(entry.no_clear_notification === true) {
6272 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6275 title = name + ' back to normal (' + value_string.toString() + ')';
6276 icon = 'images/check-mark-2-128-green.png';
6277 interaction = false;
6281 if(entry.old_status === 'CRITICAL')
6282 status = 'demoted to ' + entry.status.toLowerCase();
6284 icon = 'images/alert-128-orange.png';
6285 interaction = false;
6289 if(entry.old_status === 'WARNING')
6290 status = 'escalated to ' + entry.status.toLowerCase();
6292 icon = 'images/alert-128-red.png';
6297 console.log('invalid alarm status ' + entry.status);
6302 // cleanup old notifications with the same alarm_id as this one
6303 // FIXME: it does not seem to work on any web browser!
6304 var len = NETDATA.alarms.notifications_shown.length;
6306 var n = NETDATA.alarms.notifications_shown[len];
6307 if(n.data.alarm_id === entry.alarm_id) {
6308 console.log('removing old alarm ' + n.data.unique_id);
6310 // close the notification
6313 // remove it from the array
6314 NETDATA.alarms.notifications_shown.splice(len, 1);
6315 len = NETDATA.alarms.notifications_shown.length;
6322 setTimeout(function() {
6323 // show this notification
6324 // console.log('new notification: ' + title);
6325 var n = new Notification(title, {
6326 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6328 requireInteraction: interaction,
6329 icon: NETDATA.serverDefault + icon,
6333 n.onclick = function(event) {
6334 event.preventDefault();
6335 NETDATA.alarms.onclick(event.target.data);
6339 // NETDATA.alarms.notifications_shown.push(n);
6340 // console.log(entry);
6341 }, NETDATA.alarms.ms_penalty);
6343 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6347 scrollToChart: function(chart_id) {
6348 if(typeof chart_id === 'string') {
6349 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6350 if(typeof offset !== 'undefined') {
6351 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6358 scrollToAlarm: function(alarm) {
6359 if(typeof alarm === 'object') {
6360 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6362 if(ret === true && NETDATA.options.page_is_visible === false)
6364 // 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.');
6369 notifyAll: function() {
6370 // console.log('FETCHING ALARM LOG');
6371 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6372 // console.log('ALARM LOG FETCHED');
6374 if(data === null || typeof data !== 'object') {
6375 console.log('invalid alarms log response');
6379 if(data.length === 0) {
6380 console.log('received empty alarm log');
6384 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6386 data.sort(function(a, b) {
6387 if(a.unique_id > b.unique_id) return -1;
6388 if(a.unique_id < b.unique_id) return 1;
6392 NETDATA.alarms.ms_penalty = 0;
6394 var len = data.length;
6396 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6397 NETDATA.alarms.notify(data[len]);
6400 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6403 NETDATA.alarms.last_notification_id = data[0].unique_id;
6404 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6405 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6409 check_notifications: function() {
6410 // returns true if we should fire 1+ notifications
6412 if(NETDATA.alarms.notifications !== true) {
6413 // console.log('notifications not available');
6417 if(Notification.permission !== 'granted') {
6418 // console.log('notifications not granted');
6422 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6423 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6425 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6426 // console.log('new alarms detected');
6429 //else console.log('no new alarms');
6431 // else console.log('cannot process alarms');
6436 get: function(what, callback) {
6438 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6442 'Cache-Control': 'no-cache, no-store',
6443 'Pragma': 'no-cache'
6445 xhrFields: { withCredentials: true } // required for the cookie
6447 .done(function(data) {
6448 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6449 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6451 if(typeof callback === 'function')
6452 return callback(data);
6455 NETDATA.error(415, NETDATA.alarms.server);
6457 if(typeof callback === 'function')
6458 return callback(null);
6462 update_forever: function() {
6463 NETDATA.alarms.get('active', function(data) {
6465 NETDATA.alarms.current = data;
6467 if(NETDATA.alarms.check_notifications() === true) {
6468 NETDATA.alarms.notifyAll();
6471 if (typeof NETDATA.alarms.callback === 'function') {
6472 NETDATA.alarms.callback(data);
6475 // Health monitoring is disabled on this netdata
6476 if(data.status === false) return;
6479 setTimeout(NETDATA.alarms.update_forever, 10000);
6483 get_log: function(last_id, callback) {
6484 // console.log('fetching all log after ' + last_id.toString());
6486 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6490 'Cache-Control': 'no-cache, no-store',
6491 'Pragma': 'no-cache'
6493 xhrFields: { withCredentials: true } // required for the cookie
6495 .done(function(data) {
6496 if(typeof callback === 'function')
6497 return callback(data);
6500 NETDATA.error(416, NETDATA.alarms.server);
6502 if(typeof callback === 'function')
6503 return callback(null);
6508 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6510 NETDATA.alarms.last_notification_id =
6511 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6513 if(NETDATA.alarms.onclick === null)
6514 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6516 if(netdataShowAlarms === true) {
6517 NETDATA.alarms.update_forever();
6519 if('Notification' in window) {
6520 // console.log('notifications available');
6521 NETDATA.alarms.notifications = true;
6523 if(Notification.permission === 'default')
6524 Notification.requestPermission();
6530 // ----------------------------------------------------------------------------------------------------------------
6531 // Registry of netdata hosts
6533 NETDATA.registry = {
6534 server: null, // the netdata registry server
6535 person_guid: null, // the unique ID of this browser / user
6536 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6537 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6538 machines: null, // the user's other URLs
6539 machines_array: null, // the user's other URLs in an array
6542 parsePersonUrls: function(person_urls) {
6543 // console.log(person_urls);
6544 NETDATA.registry.person_urls = person_urls;
6547 NETDATA.registry.machines = {};
6548 NETDATA.registry.machines_array = [];
6550 var apu = person_urls;
6553 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6554 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6560 accesses: apu[i][3],
6564 obj.alternate_urls.push(apu[i][1]);
6566 NETDATA.registry.machines[apu[i][0]] = obj;
6567 NETDATA.registry.machines_array.push(obj);
6570 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6572 var pu = NETDATA.registry.machines[apu[i][0]];
6573 if(pu.last_t < apu[i][2]) {
6575 pu.last_t = apu[i][2];
6576 pu.name = apu[i][4];
6578 pu.accesses += apu[i][3];
6579 pu.alternate_urls.push(apu[i][1]);
6584 if(typeof netdataRegistryCallback === 'function')
6585 netdataRegistryCallback(NETDATA.registry.machines_array);
6589 if(netdataRegistry !== true) return;
6591 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6593 NETDATA.registry.server = data.registry;
6594 NETDATA.registry.machine_guid = data.machine_guid;
6595 NETDATA.registry.hostname = data.hostname;
6597 NETDATA.registry.access(2, function (person_urls) {
6598 NETDATA.registry.parsePersonUrls(person_urls);
6605 hello: function(host, callback) {
6606 host = NETDATA.fixHost(host);
6608 // send HELLO to a netdata server:
6609 // 1. verifies the server is reachable
6610 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6612 url: host + '/api/v1/registry?action=hello',
6616 'Cache-Control': 'no-cache, no-store',
6617 'Pragma': 'no-cache'
6619 xhrFields: { withCredentials: true } // required for the cookie
6621 .done(function(data) {
6622 if(typeof data.status !== 'string' || data.status !== 'ok') {
6623 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6627 if(typeof callback === 'function')
6628 return callback(data);
6631 NETDATA.error(407, host);
6633 if(typeof callback === 'function')
6634 return callback(null);
6638 access: function(max_redirects, callback) {
6639 // send ACCESS to a netdata registry:
6640 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6641 // 2. it responds with a list of netdata servers we know
6642 // the registry identifies us using a cookie it sets the first time we access it
6643 // the registry may respond with a redirect URL to send us to another registry
6645 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),
6649 'Cache-Control': 'no-cache, no-store',
6650 'Pragma': 'no-cache'
6652 xhrFields: { withCredentials: true } // required for the cookie
6654 .done(function(data) {
6655 var redirect = null;
6656 if(typeof data.registry === 'string')
6657 redirect = data.registry;
6659 if(typeof data.status !== 'string' || data.status !== 'ok') {
6660 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6665 if(redirect !== null && max_redirects > 0) {
6666 NETDATA.registry.server = redirect;
6667 NETDATA.registry.access(max_redirects - 1, callback);
6670 if(typeof callback === 'function')
6671 return callback(null);
6675 if(typeof data.person_guid === 'string')
6676 NETDATA.registry.person_guid = data.person_guid;
6678 if(typeof callback === 'function')
6679 return callback(data.urls);
6683 NETDATA.error(410, NETDATA.registry.server);
6685 if(typeof callback === 'function')
6686 return callback(null);
6690 delete: function(delete_url, callback) {
6691 // send DELETE to a netdata registry:
6693 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),
6697 'Cache-Control': 'no-cache, no-store',
6698 'Pragma': 'no-cache'
6700 xhrFields: { withCredentials: true } // required for the cookie
6702 .done(function(data) {
6703 if(typeof data.status !== 'string' || data.status !== 'ok') {
6704 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6708 if(typeof callback === 'function')
6709 return callback(data);
6712 NETDATA.error(412, NETDATA.registry.server);
6714 if(typeof callback === 'function')
6715 return callback(null);
6719 search: function(machine_guid, callback) {
6720 // SEARCH for the URLs of a machine:
6722 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,
6726 'Cache-Control': 'no-cache, no-store',
6727 'Pragma': 'no-cache'
6729 xhrFields: { withCredentials: true } // required for the cookie
6731 .done(function(data) {
6732 if(typeof data.status !== 'string' || data.status !== 'ok') {
6733 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6737 if(typeof callback === 'function')
6738 return callback(data);
6741 NETDATA.error(418, NETDATA.registry.server);
6743 if(typeof callback === 'function')
6744 return callback(null);
6748 switch: function(new_person_guid, callback) {
6751 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,
6755 'Cache-Control': 'no-cache, no-store',
6756 'Pragma': 'no-cache'
6758 xhrFields: { withCredentials: true } // required for the cookie
6760 .done(function(data) {
6761 if(typeof data.status !== 'string' || data.status !== 'ok') {
6762 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6766 if(typeof callback === 'function')
6767 return callback(data);
6770 NETDATA.error(414, NETDATA.registry.server);
6772 if(typeof callback === 'function')
6773 return callback(null);
6778 // ----------------------------------------------------------------------------------------------------------------
6781 if(typeof netdataPrepCallback === 'function')
6782 netdataPrepCallback();
6784 NETDATA.errorReset();
6785 NETDATA.loadRequiredCSS(0);
6787 NETDATA._loadjQuery(function() {
6788 NETDATA.loadRequiredJs(0, function() {
6789 if(typeof $().emulateTransitionEnd !== 'function') {
6790 // bootstrap is not available
6791 NETDATA.options.current.show_help = false;
6794 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6795 if(NETDATA.options.debug.main_loop === true)
6796 console.log('starting chart refresh thread');
6802 })(window, document);