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.');
3146 // check and update the legend
3147 this.legendUpdateDOM();
3149 if(this.chart_created === true
3150 && typeof this.library.update === 'function') {
3152 if(this.debug === true)
3153 this.log('updating chart...');
3155 if(callChartLibraryUpdateSafely(data) === false)
3159 if(this.debug === true)
3160 this.log('creating chart...');
3162 if(callChartLibraryCreateSafely(data) === false)
3166 this.legendShowLatestValues();
3167 if(this.selected === true)
3168 NETDATA.globalSelectionSync.stop();
3170 // update the performance counters
3171 var now = Date.now();
3172 this.tm.last_updated = now;
3174 // don't update last_autorefreshed if this chart is
3175 // forced to be updated with global PanAndZoom
3176 if(NETDATA.globalPanAndZoom.isActive())
3177 this.tm.last_autorefreshed = 0;
3179 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3180 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3182 this.tm.last_autorefreshed = now;
3185 this.refresh_dt_ms = now - started;
3186 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3188 if(this.refresh_dt_element !== null)
3189 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3192 this.updateChart = function(callback) {
3193 if(this.debug === true)
3194 this.log('updateChart() called.');
3196 if(this._updating === true) {
3197 if(this.debug === true)
3198 this.log('I am already updating...');
3200 if(typeof callback === 'function')
3206 // due to late initialization of charts and libraries
3207 // we need to check this too
3208 if(this.enabled === false) {
3209 if(this.debug === true)
3210 this.log('I am not enabled');
3212 if(typeof callback === 'function')
3218 if(canBeRendered() === false) {
3219 if(typeof callback === 'function')
3225 if(this.chart === null)
3226 return this.getChart(function() {
3227 return that.updateChart(callback);
3230 if(this.library.initialized === false) {
3231 if(this.library.enabled === true) {
3232 return this.library.initialize(function () {
3233 return that.updateChart(callback);
3237 error('chart library "' + this.library_name + '" is not available.');
3239 if(typeof callback === 'function')
3246 this.clearSelection();
3249 if(this.debug === true)
3250 this.log('updating from ' + this.data_url);
3252 NETDATA.statistics.refreshes_total++;
3253 NETDATA.statistics.refreshes_active++;
3255 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3256 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3258 this._updating = true;
3260 this.xhr = $.ajax( {
3265 'Cache-Control': 'no-cache, no-store',
3266 'Pragma': 'no-cache'
3268 xhrFields: { withCredentials: true } // required for the cookie
3270 .done(function(data) {
3271 that.xhr = undefined;
3272 that.retries_on_data_failures = 0;
3274 if(that.debug === true)
3275 that.log('data received. updating chart.');
3277 that.updateChartWithData(data);
3279 .fail(function(msg) {
3280 that.xhr = undefined;
3282 if(msg.statusText !== 'abort') {
3283 that.retries_on_data_failures++;
3284 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3285 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3286 that.retries_on_data_failures = 0;
3287 error('data download failed for url: ' + that.data_url);
3290 that.tm.last_autorefreshed = Date.now();
3291 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3295 .always(function() {
3296 that.xhr = undefined;
3298 NETDATA.statistics.refreshes_active--;
3299 that._updating = false;
3301 if(typeof callback === 'function')
3306 this.isVisible = function(nocache) {
3307 if(typeof nocache === 'undefined')
3310 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3312 // caching - we do not evaluate the charts visibility
3313 // if the page has not been scrolled since the last check
3314 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3315 return this.___isVisible___;
3317 this.tm.last_visible_check = Date.now();
3319 var wh = window.innerHeight;
3320 var x = this.element.getBoundingClientRect();
3324 if(x.width === 0 || x.height === 0) {
3326 this.___isVisible___ = false;
3327 return this.___isVisible___;
3330 if(x.top < 0 && -x.top > x.height) {
3331 // the chart is entirely above
3332 ret = -x.top - x.height;
3334 else if(x.top > wh) {
3335 // the chart is entirely below
3339 if(ret > tolerance) {
3340 // the chart is too far
3343 this.___isVisible___ = false;
3344 return this.___isVisible___;
3347 // the chart is inside or very close
3350 this.___isVisible___ = true;
3351 return this.___isVisible___;
3355 this.isAutoRefreshable = function() {
3356 return (this.current.autorefresh);
3359 this.canBeAutoRefreshed = function() {
3360 var now = Date.now();
3362 if(this.running === true) {
3363 if(this.debug === true)
3364 this.log('I am already running');
3369 if(this.enabled === false) {
3370 if(this.debug === true)
3371 this.log('I am not enabled');
3376 if(this.library === null || this.library.enabled === false) {
3377 error('charting library "' + this.library_name + '" is not available');
3378 if(this.debug === true)
3379 this.log('My chart library ' + this.library_name + ' is not available');
3384 if(this.isVisible() === false) {
3385 if(NETDATA.options.debug.visibility === true || this.debug === true)
3386 this.log('I am not visible');
3391 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3392 if(this.debug === true)
3393 this.log('timed force update detected - allowing this update');
3395 this.current.force_update_at = 0;
3399 if(this.isAutoRefreshable() === true) {
3400 // allow the first update, even if the page is not visible
3401 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3402 if(NETDATA.options.debug.focus === true || this.debug === true)
3403 this.log('canBeAutoRefreshed(): page does not have focus');
3408 if(this.needsRecreation() === true) {
3409 if(this.debug === true)
3410 this.log('canBeAutoRefreshed(): needs re-creation.');
3415 // options valid only for autoRefresh()
3416 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3417 if(NETDATA.globalPanAndZoom.isActive()) {
3418 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3419 if(this.debug === true)
3420 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3425 if(this.debug === true)
3426 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3432 if(this.selected === true) {
3433 if(this.debug === true)
3434 this.log('canBeAutoRefreshed(): I have a selection in place.');
3439 if(this.paused === true) {
3440 if(this.debug === true)
3441 this.log('canBeAutoRefreshed(): I am paused.');
3446 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3447 if(this.debug === true)
3448 this.log('canBeAutoRefreshed(): It is time to update me.');
3458 this.autoRefresh = function(callback) {
3459 if(this.canBeAutoRefreshed() === true && this.running === false) {
3462 state.running = true;
3463 state.updateChart(function() {
3464 state.running = false;
3466 if(typeof callback !== 'undefined')
3471 if(typeof callback !== 'undefined')
3476 this._defaultsFromDownloadedChart = function(chart) {
3478 this.chart_url = chart.url;
3479 this.data_update_every = chart.update_every * 1000;
3480 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3481 this.tm.last_info_downloaded = Date.now();
3483 if(this.title === null)
3484 this.title = chart.title;
3486 if(this.units === null)
3487 this.units = chart.units;
3490 // fetch the chart description from the netdata server
3491 this.getChart = function(callback) {
3492 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3494 this._defaultsFromDownloadedChart(this.chart);
3496 if(typeof callback === 'function')
3500 this.chart_url = "/api/v1/chart?chart=" + this.id;
3502 if(this.debug === true)
3503 this.log('downloading ' + this.chart_url);
3506 url: this.host + this.chart_url,
3509 xhrFields: { withCredentials: true } // required for the cookie
3511 .done(function(chart) {
3512 chart.url = that.chart_url;
3513 that._defaultsFromDownloadedChart(chart);
3514 NETDATA.chartRegistry.add(that.host, that.id, chart);
3517 NETDATA.error(404, that.chart_url);
3518 error('chart not found on url "' + that.chart_url + '"');
3520 .always(function() {
3521 if(typeof callback === 'function')
3527 // ============================================================================================================
3533 NETDATA.resetAllCharts = function(state) {
3534 // first clear the global selection sync
3535 // to make sure no chart is in selected state
3536 state.globalSelectionSyncStop();
3538 // there are 2 possibilities here
3539 // a. state is the global Pan and Zoom master
3540 // b. state is not the global Pan and Zoom master
3542 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3545 // clear the global Pan and Zoom
3546 // this will also refresh the master
3547 // and unblock any charts currently mirroring the master
3548 NETDATA.globalPanAndZoom.clearMaster();
3550 // if we were not the master, reset our status too
3551 // this is required because most probably the mouse
3552 // is over this chart, blocking it from auto-refreshing
3553 if(master === false && (state.paused === true || state.selected === true))
3557 // get or create a chart state, given a DOM element
3558 NETDATA.chartState = function(element) {
3559 var state = $(element).data('netdata-state-object') || null;
3560 if(state === null) {
3561 state = new chartState(element);
3562 $(element).data('netdata-state-object', state);
3567 // ----------------------------------------------------------------------------------------------------------------
3568 // Library functions
3570 // Load a script without jquery
3571 // This is used to load jquery - after it is loaded, we use jquery
3572 NETDATA._loadjQuery = function(callback) {
3573 if(typeof jQuery === 'undefined') {
3574 if(NETDATA.options.debug.main_loop === true)
3575 console.log('loading ' + NETDATA.jQuery);
3577 var script = document.createElement('script');
3578 script.type = 'text/javascript';
3579 script.async = true;
3580 script.src = NETDATA.jQuery;
3582 // script.onabort = onError;
3583 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3584 if(typeof callback === "function")
3585 script.onload = callback;
3587 var s = document.getElementsByTagName('script')[0];
3588 s.parentNode.insertBefore(script, s);
3590 else if(typeof callback === "function")
3594 NETDATA._loadCSS = function(filename) {
3595 // don't use jQuery here
3596 // styles are loaded before jQuery
3597 // to eliminate showing an unstyled page to the user
3599 var fileref = document.createElement("link");
3600 fileref.setAttribute("rel", "stylesheet");
3601 fileref.setAttribute("type", "text/css");
3602 fileref.setAttribute("href", filename);
3604 if (typeof fileref !== 'undefined')
3605 document.getElementsByTagName("head")[0].appendChild(fileref);
3608 NETDATA.colorHex2Rgb = function(hex) {
3609 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3610 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3611 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3612 return r + r + g + g + b + b;
3615 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3617 r: parseInt(result[1], 16),
3618 g: parseInt(result[2], 16),
3619 b: parseInt(result[3], 16)
3623 NETDATA.colorLuminance = function(hex, lum) {
3624 // validate hex string
3625 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3627 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3631 // convert to decimal and change luminosity
3632 var rgb = "#", c, i;
3633 for (i = 0; i < 3; i++) {
3634 c = parseInt(hex.substr(i*2,2), 16);
3635 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3636 rgb += ("00"+c).substr(c.length);
3642 NETDATA.guid = function() {
3644 return Math.floor((1 + Math.random()) * 0x10000)
3649 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3652 NETDATA.zeropad = function(x) {
3653 if(x > -10 && x < 10) return '0' + x.toString();
3654 else return x.toString();
3657 // user function to signal us the DOM has been
3659 NETDATA.updatedDom = function() {
3660 NETDATA.options.updated_dom = true;
3663 NETDATA.ready = function(callback) {
3664 NETDATA.options.pauseCallback = callback;
3667 NETDATA.pause = function(callback) {
3668 if(typeof callback === 'function') {
3669 if (NETDATA.options.pause === true)
3672 NETDATA.options.pauseCallback = callback;
3676 NETDATA.unpause = function() {
3677 NETDATA.options.pauseCallback = null;
3678 NETDATA.options.updated_dom = true;
3679 NETDATA.options.pause = false;
3682 // ----------------------------------------------------------------------------------------------------------------
3684 // this is purely sequential charts refresher
3685 // it is meant to be autonomous
3686 NETDATA.chartRefresherNoParallel = function(index) {
3687 if(NETDATA.options.debug.main_loop === true)
3688 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3690 if(NETDATA.options.updated_dom === true) {
3691 // the dom has been updated
3692 // get the dom parts again
3693 NETDATA.parseDom(NETDATA.chartRefresher);
3696 if(index >= NETDATA.options.targets.length) {
3697 if(NETDATA.options.debug.main_loop === true)
3698 console.log('waiting to restart main loop...');
3700 NETDATA.options.auto_refresher_fast_weight = 0;
3702 setTimeout(function() {
3703 NETDATA.chartRefresher();
3704 }, NETDATA.options.current.idle_between_loops);
3707 var state = NETDATA.options.targets[index];
3709 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3710 if(NETDATA.options.debug.main_loop === true)
3711 console.log('fast rendering...');
3713 state.autoRefresh(function() {
3714 NETDATA.chartRefresherNoParallel(++index);
3718 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3719 NETDATA.options.auto_refresher_fast_weight = 0;
3721 setTimeout(function() {
3722 state.autoRefresh(function() {
3723 NETDATA.chartRefresherNoParallel(++index);
3725 }, NETDATA.options.current.idle_between_charts);
3730 NETDATA.chartRefresherWaitTime = function() {
3731 return NETDATA.options.current.idle_parallel_loops;
3734 // the default refresher
3735 NETDATA.chartRefresher = function() {
3736 // console.log('auto-refresher...');
3738 if(NETDATA.options.pause === true) {
3739 // console.log('auto-refresher is paused');
3740 setTimeout(NETDATA.chartRefresher,
3741 NETDATA.chartRefresherWaitTime());
3745 if(typeof NETDATA.options.pauseCallback === 'function') {
3746 // console.log('auto-refresher is calling pauseCallback');
3747 NETDATA.options.pause = true;
3748 NETDATA.options.pauseCallback();
3749 NETDATA.chartRefresher();
3753 if(NETDATA.options.current.parallel_refresher === false) {
3754 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3755 NETDATA.chartRefresherNoParallel(0);
3759 if(NETDATA.options.updated_dom === true) {
3760 // the dom has been updated
3761 // get the dom parts again
3762 // console.log('auto-refresher is calling parseDom()');
3763 NETDATA.parseDom(NETDATA.chartRefresher);
3768 var targets = NETDATA.options.targets;
3769 var len = targets.length;
3772 state = targets[len];
3773 if(state.isVisible() === false || state.running === true)
3776 if(state.library.initialized === false) {
3777 if(state.library.enabled === true) {
3778 state.library.initialize(NETDATA.chartRefresher);
3782 state.error('chart library "' + state.library_name + '" is not enabled.');
3786 parallel.unshift(state);
3789 if(parallel.length > 0) {
3790 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3791 // this will execute the jobs in parallel
3792 $(parallel).each(function() {
3797 // console.log('auto-refresher nothing to do');
3800 // run the next refresh iteration
3801 setTimeout(NETDATA.chartRefresher,
3802 NETDATA.chartRefresherWaitTime());
3805 NETDATA.parseDom = function(callback) {
3806 NETDATA.options.last_page_scroll = Date.now();
3807 NETDATA.options.updated_dom = false;
3809 var targets = $('div[data-netdata]'); //.filter(':visible');
3811 if(NETDATA.options.debug.main_loop === true)
3812 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3814 NETDATA.options.targets = [];
3815 var len = targets.length;
3817 // the initialization will take care of sizing
3818 // and the "loading..." message
3819 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3822 if(typeof callback === 'function')
3826 // this is the main function - where everything starts
3827 NETDATA.start = function() {
3828 // this should be called only once
3830 NETDATA.options.page_is_visible = true;
3832 $(window).blur(function() {
3833 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3834 NETDATA.options.page_is_visible = false;
3835 if(NETDATA.options.debug.focus === true)
3836 console.log('Lost Focus!');
3840 $(window).focus(function() {
3841 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3842 NETDATA.options.page_is_visible = true;
3843 if(NETDATA.options.debug.focus === true)
3844 console.log('Focus restored!');
3848 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3849 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3850 NETDATA.options.page_is_visible = false;
3851 if(NETDATA.options.debug.focus === true)
3852 console.log('Document has no focus!');
3856 // bootstrap tab switching
3857 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3859 // bootstrap modal switching
3860 var $modal = $('.modal');
3861 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3862 $modal.on('shown.bs.modal', NETDATA.onscroll);
3864 // bootstrap collapse switching
3865 var $collapse = $('.collapse');
3866 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3867 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3869 NETDATA.parseDom(NETDATA.chartRefresher);
3871 // Alarms initialization
3872 setTimeout(NETDATA.alarms.init, 1000);
3874 // Registry initialization
3875 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3877 if(typeof netdataCallback === 'function')
3881 // ----------------------------------------------------------------------------------------------------------------
3884 NETDATA.peityInitialize = function(callback) {
3885 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3887 url: NETDATA.peity_js,
3890 xhrFields: { withCredentials: true } // required for the cookie
3893 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3896 NETDATA.chartLibraries.peity.enabled = false;
3897 NETDATA.error(100, NETDATA.peity_js);
3899 .always(function() {
3900 if(typeof callback === "function")
3905 NETDATA.chartLibraries.peity.enabled = false;
3906 if(typeof callback === "function")
3911 NETDATA.peityChartUpdate = function(state, data) {
3912 state.peity_instance.innerHTML = data.result;
3914 if(state.peity_options.stroke !== state.chartColors()[0]) {
3915 state.peity_options.stroke = state.chartColors()[0];
3916 if(state.chart.chart_type === 'line')
3917 state.peity_options.fill = NETDATA.themes.current.background;
3919 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3922 $(state.peity_instance).peity('line', state.peity_options);
3926 NETDATA.peityChartCreate = function(state, data) {
3927 state.peity_instance = document.createElement('div');
3928 state.element_chart.appendChild(state.peity_instance);
3930 var self = $(state.element);
3931 state.peity_options = {
3932 stroke: NETDATA.themes.current.foreground,
3933 strokeWidth: self.data('peity-strokewidth') || 1,
3934 width: state.chartWidth(),
3935 height: state.chartHeight(),
3936 fill: NETDATA.themes.current.foreground
3939 NETDATA.peityChartUpdate(state, data);
3943 // ----------------------------------------------------------------------------------------------------------------
3946 NETDATA.sparklineInitialize = function(callback) {
3947 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3949 url: NETDATA.sparkline_js,
3952 xhrFields: { withCredentials: true } // required for the cookie
3955 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3958 NETDATA.chartLibraries.sparkline.enabled = false;
3959 NETDATA.error(100, NETDATA.sparkline_js);
3961 .always(function() {
3962 if(typeof callback === "function")
3967 NETDATA.chartLibraries.sparkline.enabled = false;
3968 if(typeof callback === "function")
3973 NETDATA.sparklineChartUpdate = function(state, data) {
3974 state.sparkline_options.width = state.chartWidth();
3975 state.sparkline_options.height = state.chartHeight();
3977 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3981 NETDATA.sparklineChartCreate = function(state, data) {
3982 var self = $(state.element);
3983 var type = self.data('sparkline-type') || 'line';
3984 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3985 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3986 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3987 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3988 var composite = self.data('sparkline-composite') || undefined;
3989 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3990 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3991 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3992 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3993 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3994 var spotColor = self.data('sparkline-spotcolor') || undefined;
3995 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3996 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3997 var spotRadius = self.data('sparkline-spotradius') || undefined;
3998 var valueSpots = self.data('sparkline-valuespots') || undefined;
3999 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
4000 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
4001 var lineWidth = self.data('sparkline-linewidth') || undefined;
4002 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
4003 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
4004 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
4005 var xvalues = self.data('sparkline-xvalues') || undefined;
4006 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
4007 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
4008 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
4009 var disableInteraction = self.data('sparkline-disableinteraction') || false;
4010 var disableTooltips = self.data('sparkline-disabletooltips') || false;
4011 var disableHighlight = self.data('sparkline-disablehighlight') || false;
4012 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
4013 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
4014 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
4015 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
4016 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
4017 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
4018 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
4019 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
4020 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
4021 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
4022 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
4023 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
4024 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
4025 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
4026 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
4027 var animatedZooms = self.data('sparkline-animatedzooms') || false;
4029 if(spotColor === 'disable') spotColor='';
4030 if(minSpotColor === 'disable') minSpotColor='';
4031 if(maxSpotColor === 'disable') maxSpotColor='';
4033 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
4035 state.sparkline_options = {
4037 lineColor: lineColor,
4038 fillColor: fillColor,
4039 chartRangeMin: chartRangeMin,
4040 chartRangeMax: chartRangeMax,
4041 composite: composite,
4042 enableTagOptions: enableTagOptions,
4043 tagOptionPrefix: tagOptionPrefix,
4044 tagValuesAttribute: tagValuesAttribute,
4045 disableHiddenCheck: disableHiddenCheck,
4046 defaultPixelsPerValue: defaultPixelsPerValue,
4047 spotColor: spotColor,
4048 minSpotColor: minSpotColor,
4049 maxSpotColor: maxSpotColor,
4050 spotRadius: spotRadius,
4051 valueSpots: valueSpots,
4052 highlightSpotColor: highlightSpotColor,
4053 highlightLineColor: highlightLineColor,
4054 lineWidth: lineWidth,
4055 normalRangeMin: normalRangeMin,
4056 normalRangeMax: normalRangeMax,
4057 drawNormalOnTop: drawNormalOnTop,
4059 chartRangeClip: chartRangeClip,
4060 chartRangeMinX: chartRangeMinX,
4061 chartRangeMaxX: chartRangeMaxX,
4062 disableInteraction: disableInteraction,
4063 disableTooltips: disableTooltips,
4064 disableHighlight: disableHighlight,
4065 highlightLighten: highlightLighten,
4066 highlightColor: highlightColor,
4067 tooltipContainer: tooltipContainer,
4068 tooltipClassname: tooltipClassname,
4069 tooltipChartTitle: state.title,
4070 tooltipFormat: tooltipFormat,
4071 tooltipPrefix: tooltipPrefix,
4072 tooltipSuffix: tooltipSuffix,
4073 tooltipSkipNull: tooltipSkipNull,
4074 tooltipValueLookups: tooltipValueLookups,
4075 tooltipFormatFieldlist: tooltipFormatFieldlist,
4076 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4077 numberFormatter: numberFormatter,
4078 numberDigitGroupSep: numberDigitGroupSep,
4079 numberDecimalMark: numberDecimalMark,
4080 numberDigitGroupCount: numberDigitGroupCount,
4081 animatedZooms: animatedZooms,
4082 width: state.chartWidth(),
4083 height: state.chartHeight()
4086 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4090 // ----------------------------------------------------------------------------------------------------------------
4097 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4098 if(after < state.netdata_first)
4099 after = state.netdata_first;
4101 if(before > state.netdata_last)
4102 before = state.netdata_last;
4104 state.setMode('zoom');
4105 state.globalSelectionSyncStop();
4106 state.globalSelectionSyncDelay();
4107 state.dygraph_user_action = true;
4108 state.dygraph_force_zoom = true;
4109 state.updateChartPanOrZoom(after, before);
4110 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4113 NETDATA.dygraphSetSelection = function(state, t) {
4114 if(typeof state.dygraph_instance !== 'undefined') {
4115 var r = state.calculateRowForTime(t);
4117 state.dygraph_instance.setSelection(r);
4119 state.dygraph_instance.clearSelection();
4120 state.legendShowUndefined();
4127 NETDATA.dygraphClearSelection = function(state) {
4128 if(typeof state.dygraph_instance !== 'undefined') {
4129 state.dygraph_instance.clearSelection();
4134 NETDATA.dygraphSmoothInitialize = function(callback) {
4136 url: NETDATA.dygraph_smooth_js,
4139 xhrFields: { withCredentials: true } // required for the cookie
4142 NETDATA.dygraph.smooth = true;
4143 smoothPlotter.smoothing = 0.3;
4146 NETDATA.dygraph.smooth = false;
4148 .always(function() {
4149 if(typeof callback === "function")
4154 NETDATA.dygraphInitialize = function(callback) {
4155 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4157 url: NETDATA.dygraph_js,
4160 xhrFields: { withCredentials: true } // required for the cookie
4163 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4166 NETDATA.chartLibraries.dygraph.enabled = false;
4167 NETDATA.error(100, NETDATA.dygraph_js);
4169 .always(function() {
4170 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4171 NETDATA.dygraphSmoothInitialize(callback);
4172 else if(typeof callback === "function")
4177 NETDATA.chartLibraries.dygraph.enabled = false;
4178 if(typeof callback === "function")
4183 NETDATA.dygraphChartUpdate = function(state, data) {
4184 var dygraph = state.dygraph_instance;
4186 if(typeof dygraph === 'undefined')
4187 return NETDATA.dygraphChartCreate(state, data);
4189 // when the chart is not visible, and hidden
4190 // if there is a window resize, dygraph detects
4191 // its element size as 0x0.
4192 // this will make it re-appear properly
4194 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4198 file: data.result.data,
4199 colors: state.chartColors(),
4200 labels: data.result.labels,
4201 labelsDivWidth: state.chartWidth() - 70,
4202 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4205 if(state.dygraph_force_zoom === true) {
4206 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4207 state.log('dygraphChartUpdate() forced zoom update');
4209 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4210 options.isZoomedIgnoreProgrammaticZoom = true;
4211 state.dygraph_force_zoom = false;
4213 else if(state.current.name !== 'auto') {
4214 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4215 state.log('dygraphChartUpdate() loose update');
4218 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4219 state.log('dygraphChartUpdate() strict update');
4221 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4222 options.isZoomedIgnoreProgrammaticZoom = true;
4225 options.valueRange = state.dygraph_options.valueRange;
4227 var oldMax = null, oldMin = null;
4228 if(state.__commonMin !== null) {
4229 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4230 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4232 if(state.__commonMax !== null) {
4233 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4234 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4237 if(state.dygraph_smooth_eligible === true) {
4238 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4239 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4240 NETDATA.dygraphChartCreate(state, data);
4245 dygraph.updateOptions(options);
4248 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4249 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4250 options.valueRange[0] = NETDATA.commonMin.get(state);
4253 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4254 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4255 options.valueRange[1] = NETDATA.commonMax.get(state);
4259 if(redraw === true) {
4260 // state.log('forcing redraw to adapt to common- min/max');
4261 dygraph.updateOptions(options);
4264 state.dygraph_last_rendered = Date.now();
4268 NETDATA.dygraphChartCreate = function(state, data) {
4269 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4270 state.log('dygraphChartCreate()');
4272 var self = $(state.element);
4274 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4275 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4277 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4279 var smooth = (NETDATA.dygraph.smooth === true)
4280 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4283 state.dygraph_options = {
4284 colors: self.data('dygraph-colors') || state.chartColors(),
4286 // leave a few pixels empty on the right of the chart
4287 rightGap: self.data('dygraph-rightgap')
4290 showRangeSelector: self.data('dygraph-showrangeselector')
4293 showRoller: self.data('dygraph-showroller')
4296 title: self.data('dygraph-title')
4299 titleHeight: self.data('dygraph-titleheight')
4302 legend: self.data('dygraph-legend')
4303 || 'always', // we need this to get selection events
4305 labels: data.result.labels,
4307 labelsDiv: self.data('dygraph-labelsdiv')
4308 || state.element_legend_childs.hidden,
4310 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4311 || { 'fontSize':'1px' },
4313 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4314 || state.chartWidth() - 70,
4316 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4319 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4325 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4328 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4331 includeZero: self.data('dygraph-includezero')
4332 || (chart_type === 'stacked'),
4334 xRangePad: self.data('dygraph-xrangepad')
4337 yRangePad: self.data('dygraph-yrangepad')
4340 valueRange: self.data('dygraph-valuerange')
4343 ylabel: state.units,
4345 yLabelWidth: self.data('dygraph-ylabelwidth')
4348 // the function to plot the chart
4351 // The width of the lines connecting data points.
4352 // This can be used to increase the contrast or some graphs.
4353 strokeWidth: self.data('dygraph-strokewidth')
4354 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4356 strokePattern: self.data('dygraph-strokepattern')
4359 // The size of the dot to draw on each point in pixels (see drawPoints).
4360 // A dot is always drawn when a point is "isolated",
4361 // i.e. there is a missing point on either side of it.
4362 // This also controls the size of those dots.
4363 drawPoints: self.data('dygraph-drawpoints')
4366 // Draw points at the edges of gaps in the data.
4367 // This improves visibility of small data segments or other data irregularities.
4368 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4371 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4374 pointSize: self.data('dygraph-pointsize')
4377 // enabling this makes the chart with little square lines
4378 stepPlot: self.data('dygraph-stepplot')
4381 // Draw a border around graph lines to make crossing lines more easily
4382 // distinguishable. Useful for graphs with many lines.
4383 strokeBorderColor: self.data('dygraph-strokebordercolor')
4384 || NETDATA.themes.current.background,
4386 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4387 || (chart_type === 'stacked')?0.0:0.0,
4389 fillGraph: self.data('dygraph-fillgraph')
4390 || (chart_type === 'area' || chart_type === 'stacked'),
4392 fillAlpha: self.data('dygraph-fillalpha')
4393 || ((chart_type === 'stacked')
4394 ?NETDATA.options.current.color_fill_opacity_stacked
4395 :NETDATA.options.current.color_fill_opacity_area),
4397 stackedGraph: self.data('dygraph-stackedgraph')
4398 || (chart_type === 'stacked'),
4400 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4403 drawAxis: self.data('dygraph-drawaxis')
4406 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4409 axisLineColor: self.data('dygraph-axislinecolor')
4410 || NETDATA.themes.current.axis,
4412 axisLineWidth: self.data('dygraph-axislinewidth')
4415 drawGrid: self.data('dygraph-drawgrid')
4418 gridLinePattern: self.data('dygraph-gridlinepattern')
4421 gridLineWidth: self.data('dygraph-gridlinewidth')
4424 gridLineColor: self.data('dygraph-gridlinecolor')
4425 || NETDATA.themes.current.grid,
4427 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4430 sigFigs: self.data('dygraph-sigfigs')
4433 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4436 valueFormatter: self.data('dygraph-valueformatter')
4439 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4440 || highlightCircleSize,
4442 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4443 || null, // TOO SLOW: { strokeWidth: 1.5 },
4445 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4446 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4448 pointClickCallback: self.data('dygraph-pointclickcallback')
4451 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4456 ticker: Dygraph.dateTicker,
4457 axisLabelFormatter: function (d, gran) {
4459 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4464 axisLabelFormatter: function (y) {
4466 // unfortunately, we have to call this every single time
4467 state.legendFormatValueDecimalsFromMinMax(
4468 this.axes_[0].extremeRange[0],
4469 this.axes_[0].extremeRange[1]
4472 return state.legendFormatValue(y);
4476 legendFormatter: function(data) {
4477 var elements = state.element_legend_childs;
4479 // if the hidden div is not there
4480 // we are not managing the legend
4481 if(elements.hidden === null) return;
4483 if (typeof data.x !== 'undefined') {
4484 state.legendSetDate(data.x);
4485 var i = data.series.length;
4487 var series = data.series[i];
4488 if(series.isVisible === true)
4489 state.legendSetLabelValue(series.label, series.y);
4491 state.legendSetLabelValue(series.label, null);
4497 drawCallback: function(dygraph, is_initial) {
4498 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4499 state.dygraph_user_action = false;
4501 var x_range = dygraph.xAxisRange();
4502 var after = Math.round(x_range[0]);
4503 var before = Math.round(x_range[1]);
4505 if(NETDATA.options.debug.dygraph === true)
4506 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4508 if(before <= state.netdata_last && after >= state.netdata_first)
4509 state.updateChartPanOrZoom(after, before);
4512 zoomCallback: function(minDate, maxDate, yRanges) {
4515 if(NETDATA.options.debug.dygraph === true)
4516 state.log('dygraphZoomCallback()');
4518 state.globalSelectionSyncStop();
4519 state.globalSelectionSyncDelay();
4520 state.setMode('zoom');
4522 // refresh it to the greatest possible zoom level
4523 state.dygraph_user_action = true;
4524 state.dygraph_force_zoom = true;
4525 state.updateChartPanOrZoom(minDate, maxDate);
4527 highlightCallback: function(event, x, points, row, seriesName) {
4530 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4531 state.log('dygraphHighlightCallback()');
4535 // there is a bug in dygraph when the chart is zoomed enough
4536 // the time it thinks is selected is wrong
4537 // here we calculate the time t based on the row number selected
4539 // var t = state.data_after + row * state.data_update_every;
4540 // 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);
4542 state.globalSelectionSync(x);
4544 // fix legend zIndex using the internal structures of dygraph legend module
4545 // this works, but it is a hack!
4546 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4548 unhighlightCallback: function(event) {
4551 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4552 state.log('dygraphUnhighlightCallback()');
4554 state.unpauseChart();
4555 state.globalSelectionSyncStop();
4557 interactionModel : {
4558 mousedown: function(event, dygraph, context) {
4559 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4560 state.log('interactionModel.mousedown()');
4562 state.dygraph_user_action = true;
4563 state.globalSelectionSyncStop();
4565 if(NETDATA.options.debug.dygraph === true)
4566 state.log('dygraphMouseDown()');
4568 // Right-click should not initiate a zoom.
4569 if(event.button && event.button === 2) return;
4571 context.initializeMouseDown(event, dygraph, context);
4573 if(event.button && event.button === 1) {
4574 if (event.altKey || event.shiftKey) {
4575 state.setMode('pan');
4576 state.globalSelectionSyncDelay();
4577 Dygraph.startPan(event, dygraph, context);
4580 state.setMode('zoom');
4581 state.globalSelectionSyncDelay();
4582 Dygraph.startZoom(event, dygraph, context);
4586 if (event.altKey || event.shiftKey) {
4587 state.setMode('zoom');
4588 state.globalSelectionSyncDelay();
4589 Dygraph.startZoom(event, dygraph, context);
4592 state.setMode('pan');
4593 state.globalSelectionSyncDelay();
4594 Dygraph.startPan(event, dygraph, context);
4598 mousemove: function(event, dygraph, context) {
4599 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4600 state.log('interactionModel.mousemove()');
4602 if(context.isPanning) {
4603 state.dygraph_user_action = true;
4604 state.globalSelectionSyncStop();
4605 state.globalSelectionSyncDelay();
4606 state.setMode('pan');
4607 context.is2DPan = false;
4608 Dygraph.movePan(event, dygraph, context);
4610 else if(context.isZooming) {
4611 state.dygraph_user_action = true;
4612 state.globalSelectionSyncStop();
4613 state.globalSelectionSyncDelay();
4614 state.setMode('zoom');
4615 Dygraph.moveZoom(event, dygraph, context);
4618 mouseup: function(event, dygraph, context) {
4619 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4620 state.log('interactionModel.mouseup()');
4622 if (context.isPanning) {
4623 state.dygraph_user_action = true;
4624 state.globalSelectionSyncDelay();
4625 Dygraph.endPan(event, dygraph, context);
4627 else if (context.isZooming) {
4628 state.dygraph_user_action = true;
4629 state.globalSelectionSyncDelay();
4630 Dygraph.endZoom(event, dygraph, context);
4633 click: function(event, dygraph, context) {
4637 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4638 state.log('interactionModel.click()');
4640 event.preventDefault();
4642 dblclick: function(event, dygraph, context) {
4647 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4648 state.log('interactionModel.dblclick()');
4649 NETDATA.resetAllCharts(state);
4651 wheel: function(event, dygraph, context) {
4654 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4655 state.log('interactionModel.wheel()');
4657 // Take the offset of a mouse event on the dygraph canvas and
4658 // convert it to a pair of percentages from the bottom left.
4659 // (Not top left, bottom is where the lower value is.)
4660 function offsetToPercentage(g, offsetX, offsetY) {
4661 // This is calculating the pixel offset of the leftmost date.
4662 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4663 var yar0 = g.yAxisRange(0);
4665 // This is calculating the pixel of the highest value. (Top pixel)
4666 var yOffset = g.toDomCoords(null, yar0[1])[1];
4668 // x y w and h are relative to the corner of the drawing area,
4669 // so that the upper corner of the drawing area is (0, 0).
4670 var x = offsetX - xOffset;
4671 var y = offsetY - yOffset;
4673 // This is computing the rightmost pixel, effectively defining the
4675 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4677 // This is computing the lowest pixel, effectively defining the height.
4678 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4680 // Percentage from the left.
4681 var xPct = w === 0 ? 0 : (x / w);
4682 // Percentage from the top.
4683 var yPct = h === 0 ? 0 : (y / h);
4685 // The (1-) part below changes it from "% distance down from the top"
4686 // to "% distance up from the bottom".
4687 return [xPct, (1-yPct)];
4690 // Adjusts [x, y] toward each other by zoomInPercentage%
4691 // Split it so the left/bottom axis gets xBias/yBias of that change and
4692 // tight/top gets (1-xBias)/(1-yBias) of that change.
4694 // If a bias is missing it splits it down the middle.
4695 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4696 xBias = xBias || 0.5;
4697 yBias = yBias || 0.5;
4699 function adjustAxis(axis, zoomInPercentage, bias) {
4700 var delta = axis[1] - axis[0];
4701 var increment = delta * zoomInPercentage;
4702 var foo = [increment * bias, increment * (1-bias)];
4704 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4707 var yAxes = g.yAxisRanges();
4709 for (var i = 0; i < yAxes.length; i++) {
4710 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4713 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4716 if(event.altKey || event.shiftKey) {
4717 state.dygraph_user_action = true;
4719 state.globalSelectionSyncStop();
4720 state.globalSelectionSyncDelay();
4722 // http://dygraphs.com/gallery/interaction-api.js
4724 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4726 normal_def = event.wheelDelta / 40;
4729 normal_def = event.deltaY * -1.2;
4731 var normal = (event.detail) ? event.detail * -1 : normal_def;
4732 var percentage = normal / 50;
4734 if (!(event.offsetX && event.offsetY)){
4735 event.offsetX = event.layerX - event.target.offsetLeft;
4736 event.offsetY = event.layerY - event.target.offsetTop;
4739 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4740 var xPct = percentages[0];
4741 var yPct = percentages[1];
4743 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4744 var after = new_x_range[0];
4745 var before = new_x_range[1];
4747 var first = state.netdata_first + state.data_update_every;
4748 var last = state.netdata_last + state.data_update_every;
4751 after -= (before - last);
4758 state.setMode('zoom');
4759 if(state.updateChartPanOrZoom(after, before) === true)
4760 dygraph.updateOptions({ dateWindow: [ after, before ] });
4762 event.preventDefault();
4765 touchstart: function(event, dygraph, context) {
4766 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4767 state.log('interactionModel.touchstart()');
4769 state.dygraph_user_action = true;
4770 state.setMode('zoom');
4773 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4775 // we overwrite the touch directions at the end, to overwrite
4776 // the internal default of dygraph
4777 context.touchDirections = { x: true, y: false };
4779 state.dygraph_last_touch_start = Date.now();
4780 state.dygraph_last_touch_move = 0;
4782 if(typeof event.touches[0].pageX === 'number')
4783 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4785 state.dygraph_last_touch_page_x = 0;
4787 touchmove: function(event, dygraph, context) {
4788 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4789 state.log('interactionModel.touchmove()');
4791 state.dygraph_user_action = true;
4792 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4794 state.dygraph_last_touch_move = Date.now();
4796 touchend: function(event, dygraph, context) {
4797 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4798 state.log('interactionModel.touchend()');
4800 state.dygraph_user_action = true;
4801 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4803 // if it didn't move, it is a selection
4804 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4805 // internal api of dygraph
4806 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4807 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4808 if(NETDATA.dygraphSetSelection(state, t) === true)
4809 state.globalSelectionSync(t);
4812 // if it was double tap within double click time, reset the charts
4813 var now = Date.now();
4814 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4815 if(state.dygraph_last_touch_move === 0) {
4816 var dt = now - state.dygraph_last_touch_end;
4817 if(dt <= NETDATA.options.current.double_click_speed)
4818 NETDATA.resetAllCharts(state);
4822 // remember the timestamp of the last touch end
4823 state.dygraph_last_touch_end = now;
4828 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4829 state.dygraph_options.drawGrid = false;
4830 state.dygraph_options.drawAxis = false;
4831 state.dygraph_options.title = undefined;
4832 state.dygraph_options.ylabel = undefined;
4833 state.dygraph_options.yLabelWidth = 0;
4834 state.dygraph_options.labelsDivWidth = 120;
4835 state.dygraph_options.labelsDivStyles.width = '120px';
4836 state.dygraph_options.labelsSeparateLines = true;
4837 state.dygraph_options.rightGap = 0;
4838 state.dygraph_options.yRangePad = 1;
4841 if(smooth === true) {
4842 state.dygraph_smooth_eligible = true;
4844 if(NETDATA.options.current.smooth_plot === true)
4845 state.dygraph_options.plotter = smoothPlotter;
4847 else state.dygraph_smooth_eligible = false;
4849 state.dygraph_instance = new Dygraph(state.element_chart,
4850 data.result.data, state.dygraph_options);
4852 state.dygraph_force_zoom = false;
4853 state.dygraph_user_action = false;
4854 state.dygraph_last_rendered = Date.now();
4856 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4857 state.__commonMin = self.data('common-min') || null;
4858 state.__commonMax = self.data('common-max') || null;
4861 state.log('incompatible version of Dygraph detected');
4862 state.__commonMin = null;
4863 state.__commonMax = null;
4869 // ----------------------------------------------------------------------------------------------------------------
4872 NETDATA.morrisInitialize = function(callback) {
4873 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4875 // morris requires raphael
4876 if(!NETDATA.chartLibraries.raphael.initialized) {
4877 if(NETDATA.chartLibraries.raphael.enabled) {
4878 NETDATA.raphaelInitialize(function() {
4879 NETDATA.morrisInitialize(callback);
4883 NETDATA.chartLibraries.morris.enabled = false;
4884 if(typeof callback === "function")
4889 NETDATA._loadCSS(NETDATA.morris_css);
4892 url: NETDATA.morris_js,
4895 xhrFields: { withCredentials: true } // required for the cookie
4898 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4901 NETDATA.chartLibraries.morris.enabled = false;
4902 NETDATA.error(100, NETDATA.morris_js);
4904 .always(function() {
4905 if(typeof callback === "function")
4911 NETDATA.chartLibraries.morris.enabled = false;
4912 if(typeof callback === "function")
4917 NETDATA.morrisChartUpdate = function(state, data) {
4918 state.morris_instance.setData(data.result.data);
4922 NETDATA.morrisChartCreate = function(state, data) {
4924 state.morris_options = {
4925 element: state.element_chart.id,
4926 data: data.result.data,
4928 ykeys: data.dimension_names,
4929 labels: data.dimension_names,
4935 continuousLine: false,
4936 behaveLikeLine: false
4939 if(state.chart.chart_type === 'line')
4940 state.morris_instance = new Morris.Line(state.morris_options);
4942 else if(state.chart.chart_type === 'area') {
4943 state.morris_options.behaveLikeLine = true;
4944 state.morris_instance = new Morris.Area(state.morris_options);
4947 state.morris_instance = new Morris.Area(state.morris_options);
4952 // ----------------------------------------------------------------------------------------------------------------
4955 NETDATA.raphaelInitialize = function(callback) {
4956 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4958 url: NETDATA.raphael_js,
4961 xhrFields: { withCredentials: true } // required for the cookie
4964 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4967 NETDATA.chartLibraries.raphael.enabled = false;
4968 NETDATA.error(100, NETDATA.raphael_js);
4970 .always(function() {
4971 if(typeof callback === "function")
4976 NETDATA.chartLibraries.raphael.enabled = false;
4977 if(typeof callback === "function")
4982 NETDATA.raphaelChartUpdate = function(state, data) {
4983 $(state.element_chart).raphael(data.result, {
4984 width: state.chartWidth(),
4985 height: state.chartHeight()
4991 NETDATA.raphaelChartCreate = function(state, data) {
4992 $(state.element_chart).raphael(data.result, {
4993 width: state.chartWidth(),
4994 height: state.chartHeight()
5000 // ----------------------------------------------------------------------------------------------------------------
5003 NETDATA.c3Initialize = function(callback) {
5004 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
5007 if(!NETDATA.chartLibraries.d3.initialized) {
5008 if(NETDATA.chartLibraries.d3.enabled) {
5009 NETDATA.d3Initialize(function() {
5010 NETDATA.c3Initialize(callback);
5014 NETDATA.chartLibraries.c3.enabled = false;
5015 if(typeof callback === "function")
5020 NETDATA._loadCSS(NETDATA.c3_css);
5026 xhrFields: { withCredentials: true } // required for the cookie
5029 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
5032 NETDATA.chartLibraries.c3.enabled = false;
5033 NETDATA.error(100, NETDATA.c3_js);
5035 .always(function() {
5036 if(typeof callback === "function")
5042 NETDATA.chartLibraries.c3.enabled = false;
5043 if(typeof callback === "function")
5048 NETDATA.c3ChartUpdate = function(state, data) {
5049 state.c3_instance.destroy();
5050 return NETDATA.c3ChartCreate(state, data);
5052 //state.c3_instance.load({
5053 // rows: data.result,
5060 NETDATA.c3ChartCreate = function(state, data) {
5062 state.element_chart.id = 'c3-' + state.uuid;
5063 // console.log('id = ' + state.element_chart.id);
5065 state.c3_instance = c3.generate({
5066 bindto: '#' + state.element_chart.id,
5068 width: state.chartWidth(),
5069 height: state.chartHeight()
5072 pattern: state.chartColors()
5077 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5083 format: function(x) {
5084 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5111 // console.log(state.c3_instance);
5116 // ----------------------------------------------------------------------------------------------------------------
5119 NETDATA.d3Initialize = function(callback) {
5120 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5125 xhrFields: { withCredentials: true } // required for the cookie
5128 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5131 NETDATA.chartLibraries.d3.enabled = false;
5132 NETDATA.error(100, NETDATA.d3_js);
5134 .always(function() {
5135 if(typeof callback === "function")
5140 NETDATA.chartLibraries.d3.enabled = false;
5141 if(typeof callback === "function")
5146 NETDATA.d3ChartUpdate = function(state, data) {
5153 NETDATA.d3ChartCreate = function(state, data) {
5160 // ----------------------------------------------------------------------------------------------------------------
5163 NETDATA.googleInitialize = function(callback) {
5164 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5166 url: NETDATA.google_js,
5169 xhrFields: { withCredentials: true } // required for the cookie
5172 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5173 google.load('visualization', '1.1', {
5174 'packages': ['corechart', 'controls'],
5175 'callback': callback
5179 NETDATA.chartLibraries.google.enabled = false;
5180 NETDATA.error(100, NETDATA.google_js);
5181 if(typeof callback === "function")
5186 NETDATA.chartLibraries.google.enabled = false;
5187 if(typeof callback === "function")
5192 NETDATA.googleChartUpdate = function(state, data) {
5193 var datatable = new google.visualization.DataTable(data.result);
5194 state.google_instance.draw(datatable, state.google_options);
5198 NETDATA.googleChartCreate = function(state, data) {
5199 var datatable = new google.visualization.DataTable(data.result);
5201 state.google_options = {
5202 colors: state.chartColors(),
5204 // do not set width, height - the chart resizes itself
5205 //width: state.chartWidth(),
5206 //height: state.chartHeight(),
5211 // title: "Time of Day",
5212 // format:'HH:mm:ss',
5213 viewWindowMode: 'maximized',
5225 viewWindowMode: 'pretty',
5240 focusTarget: 'category',
5247 titlePosition: 'out',
5258 curveType: 'function',
5263 switch(state.chart.chart_type) {
5265 state.google_options.vAxis.viewWindowMode = 'maximized';
5266 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5267 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5271 state.google_options.isStacked = true;
5272 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5273 state.google_options.vAxis.viewWindowMode = 'maximized';
5274 state.google_options.vAxis.minValue = null;
5275 state.google_options.vAxis.maxValue = null;
5276 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5281 state.google_options.lineWidth = 2;
5282 state.google_instance = new google.visualization.LineChart(state.element_chart);
5286 state.google_instance.draw(datatable, state.google_options);
5290 // ----------------------------------------------------------------------------------------------------------------
5292 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5293 if(typeof value !== 'number') value = 0;
5294 if(typeof min !== 'number') min = 0;
5295 if(typeof max !== 'number') max = 0;
5297 if(min > value) min = value;
5298 if(max < value) max = value;
5300 // make sure it is zero based
5301 if(min > 0) min = 0;
5302 if(max < 0) max = 0;
5307 pcent = Math.round(value * 100 / max);
5308 if(pcent === 0) pcent = 0.1;
5312 pcent = Math.round(-value * 100 / min);
5313 if(pcent === 0) pcent = -0.1;
5319 // ----------------------------------------------------------------------------------------------------------------
5322 NETDATA.easypiechartInitialize = function(callback) {
5323 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5325 url: NETDATA.easypiechart_js,
5328 xhrFields: { withCredentials: true } // required for the cookie
5331 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5334 NETDATA.chartLibraries.easypiechart.enabled = false;
5335 NETDATA.error(100, NETDATA.easypiechart_js);
5337 .always(function() {
5338 if(typeof callback === "function")
5343 NETDATA.chartLibraries.easypiechart.enabled = false;
5344 if(typeof callback === "function")
5349 NETDATA.easypiechartClearSelection = function(state) {
5350 if(typeof state.easyPieChartEvent !== 'undefined') {
5351 if(state.easyPieChartEvent.timer !== undefined) {
5352 clearTimeout(state.easyPieChartEvent.timer);
5355 state.easyPieChartEvent.timer = undefined;
5358 if(state.isAutoRefreshable() === true && state.data !== null) {
5359 NETDATA.easypiechartChartUpdate(state, state.data);
5362 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5363 state.easyPieChart_instance.update(0);
5365 state.easyPieChart_instance.enableAnimation();
5370 NETDATA.easypiechartSetSelection = function(state, t) {
5371 if(state.timeIsVisible(t) !== true)
5372 return NETDATA.easypiechartClearSelection(state);
5374 var slot = state.calculateRowForTime(t);
5375 if(slot < 0 || slot >= state.data.result.length)
5376 return NETDATA.easypiechartClearSelection(state);
5378 if(typeof state.easyPieChartEvent === 'undefined') {
5379 state.easyPieChartEvent = {
5386 var value = state.data.result[state.data.result.length - 1 - slot];
5387 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5388 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5389 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5391 state.easyPieChartEvent.value = value;
5392 state.easyPieChartEvent.pcent = pcent;
5393 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5395 if(state.easyPieChartEvent.timer === undefined) {
5396 state.easyPieChart_instance.disableAnimation();
5398 state.easyPieChartEvent.timer = setTimeout(function() {
5399 state.easyPieChartEvent.timer = undefined;
5400 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5401 }, NETDATA.options.current.charts_selection_animation_delay);
5407 NETDATA.easypiechartChartUpdate = function(state, data) {
5408 var value, min, max, pcent;
5410 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5415 value = data.result[0];
5416 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5417 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5418 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5421 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5422 state.easyPieChart_instance.update(pcent);
5426 NETDATA.easypiechartChartCreate = function(state, data) {
5427 var self = $(state.element);
5428 var chart = $(state.element_chart);
5430 var value = data.result[0];
5431 var min = self.data('easypiechart-min-value') || null;
5432 var max = self.data('easypiechart-max-value') || null;
5433 var adjust = self.data('easypiechart-adjust') || null;
5436 min = NETDATA.commonMin.get(state);
5437 state.easyPieChartMin = null;
5440 state.easyPieChartMin = min;
5443 max = NETDATA.commonMax.get(state);
5444 state.easyPieChartMax = null;
5447 state.easyPieChartMax = max;
5449 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5451 chart.data('data-percent', pcent);
5455 case 'width': size = state.chartHeight(); break;
5456 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5457 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5459 default: size = state.chartWidth(); break;
5461 state.element.style.width = size + 'px';
5462 state.element.style.height = size + 'px';
5464 var stroke = Math.floor(size / 22);
5465 if(stroke < 3) stroke = 2;
5467 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5468 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5469 state.easyPieChartLabel = document.createElement('span');
5470 state.easyPieChartLabel.className = 'easyPieChartLabel';
5471 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5472 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5473 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5474 state.element_chart.appendChild(state.easyPieChartLabel);
5476 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5477 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5478 state.easyPieChartTitle = document.createElement('span');
5479 state.easyPieChartTitle.className = 'easyPieChartTitle';
5480 state.easyPieChartTitle.innerText = state.title;
5481 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5482 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5483 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5484 state.element_chart.appendChild(state.easyPieChartTitle);
5486 var unitfontsize = Math.round(titlefontsize * 0.9);
5487 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5488 state.easyPieChartUnits = document.createElement('span');
5489 state.easyPieChartUnits.className = 'easyPieChartUnits';
5490 state.easyPieChartUnits.innerText = state.units;
5491 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5492 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5493 state.element_chart.appendChild(state.easyPieChartUnits);
5495 var barColor = self.data('easypiechart-barcolor');
5496 if(typeof barColor === 'undefined' || barColor === null)
5497 barColor = state.chartColors()[0];
5499 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5500 var tmp = eval(barColor);
5501 if(typeof tmp === 'function')
5505 chart.easyPieChart({
5507 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5508 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5509 scaleLength: self.data('easypiechart-scalelength') || 5,
5510 lineCap: self.data('easypiechart-linecap') || 'round',
5511 lineWidth: self.data('easypiechart-linewidth') || stroke,
5512 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5513 size: self.data('easypiechart-size') || size,
5514 rotate: self.data('easypiechart-rotate') || 0,
5515 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5516 easing: self.data('easypiechart-easing') || undefined
5519 // when we just re-create the chart
5520 // do not animate the first update
5522 if(typeof state.easyPieChart_instance !== 'undefined')
5525 state.easyPieChart_instance = chart.data('easyPieChart');
5526 if(animate === false) state.easyPieChart_instance.disableAnimation();
5527 state.easyPieChart_instance.update(pcent);
5528 if(animate === false) state.easyPieChart_instance.enableAnimation();
5532 // ----------------------------------------------------------------------------------------------------------------
5535 NETDATA.gaugeInitialize = function(callback) {
5536 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5538 url: NETDATA.gauge_js,
5541 xhrFields: { withCredentials: true } // required for the cookie
5544 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5547 NETDATA.chartLibraries.gauge.enabled = false;
5548 NETDATA.error(100, NETDATA.gauge_js);
5550 .always(function() {
5551 if(typeof callback === "function")
5556 NETDATA.chartLibraries.gauge.enabled = false;
5557 if(typeof callback === "function")
5562 NETDATA.gaugeAnimation = function(state, status) {
5565 if(typeof status === 'boolean' && status === false)
5567 else if(typeof status === 'number')
5570 // console.log('gauge speed ' + speed);
5571 state.gauge_instance.animationSpeed = speed;
5572 state.___gaugeOld__.speed = speed;
5575 NETDATA.gaugeSet = function(state, value, min, max) {
5576 if(typeof value !== 'number') value = 0;
5577 if(typeof min !== 'number') min = 0;
5578 if(typeof max !== 'number') max = 0;
5579 if(value > max) max = value;
5580 if(value < min) min = value;
5586 else if(min === max)
5589 // gauge.js has an issue if the needle
5590 // is smaller than min or larger than max
5591 // when we set the new values
5592 // the needle will go crazy
5594 // to prevent it, we always feed it
5595 // with a percentage, so that the needle
5596 // is always between min and max
5597 var pcent = (value - min) * 100 / (max - min);
5599 // bug fix for gauge.js 1.3.1
5600 // if the value is the absolute min or max, the chart is broken
5601 if(pcent < 0.001) pcent = 0.001;
5602 if(pcent > 99.999) pcent = 99.999;
5604 state.gauge_instance.set(pcent);
5605 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5607 state.___gaugeOld__.value = value;
5608 state.___gaugeOld__.min = min;
5609 state.___gaugeOld__.max = max;
5612 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5613 if(state.___gaugeOld__.valueLabel !== value) {
5614 state.___gaugeOld__.valueLabel = value;
5615 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5617 if(state.___gaugeOld__.minLabel !== min) {
5618 state.___gaugeOld__.minLabel = min;
5619 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5621 if(state.___gaugeOld__.maxLabel !== max) {
5622 state.___gaugeOld__.maxLabel = max;
5623 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5627 NETDATA.gaugeClearSelection = function(state) {
5628 if(typeof state.gaugeEvent !== 'undefined') {
5629 if(state.gaugeEvent.timer !== undefined) {
5630 clearTimeout(state.gaugeEvent.timer);
5633 state.gaugeEvent.timer = undefined;
5636 if(state.isAutoRefreshable() === true && state.data !== null) {
5637 NETDATA.gaugeChartUpdate(state, state.data);
5640 NETDATA.gaugeAnimation(state, false);
5641 NETDATA.gaugeSet(state, null, null, null);
5642 NETDATA.gaugeSetLabels(state, null, null, null);
5645 NETDATA.gaugeAnimation(state, true);
5649 NETDATA.gaugeSetSelection = function(state, t) {
5650 if(state.timeIsVisible(t) !== true)
5651 return NETDATA.gaugeClearSelection(state);
5653 var slot = state.calculateRowForTime(t);
5654 if(slot < 0 || slot >= state.data.result.length)
5655 return NETDATA.gaugeClearSelection(state);
5657 if(typeof state.gaugeEvent === 'undefined') {
5658 state.gaugeEvent = {
5666 var value = state.data.result[state.data.result.length - 1 - slot];
5667 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5668 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5670 // make sure it is zero based
5671 if(min > 0) min = 0;
5672 if(max < 0) max = 0;
5674 state.gaugeEvent.value = value;
5675 state.gaugeEvent.min = min;
5676 state.gaugeEvent.max = max;
5677 NETDATA.gaugeSetLabels(state, value, min, max);
5679 if(state.gaugeEvent.timer === undefined) {
5680 NETDATA.gaugeAnimation(state, false);
5682 state.gaugeEvent.timer = setTimeout(function() {
5683 state.gaugeEvent.timer = undefined;
5684 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5685 }, NETDATA.options.current.charts_selection_animation_delay);
5691 NETDATA.gaugeChartUpdate = function(state, data) {
5692 var value, min, max;
5694 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5698 NETDATA.gaugeSetLabels(state, null, null, null);
5701 value = data.result[0];
5702 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5703 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5704 if(value < min) min = value;
5705 if(value > max) max = value;
5707 // make sure it is zero based
5708 if(min > 0) min = 0;
5709 if(max < 0) max = 0;
5711 NETDATA.gaugeSetLabels(state, value, min, max);
5714 NETDATA.gaugeSet(state, value, min, max);
5718 NETDATA.gaugeChartCreate = function(state, data) {
5719 var self = $(state.element);
5720 // var chart = $(state.element_chart);
5722 var value = data.result[0];
5723 var min = self.data('gauge-min-value') || null;
5724 var max = self.data('gauge-max-value') || null;
5725 var adjust = self.data('gauge-adjust') || null;
5726 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5727 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5728 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5729 var stopColor = self.data('gauge-stop-color') || void 0;
5730 var generateGradient = self.data('gauge-generate-gradient') || false;
5733 min = NETDATA.commonMin.get(state);
5734 state.gaugeMin = null;
5737 state.gaugeMin = min;
5740 max = NETDATA.commonMax.get(state);
5741 state.gaugeMax = null;
5744 state.gaugeMax = max;
5746 // make sure it is zero based
5747 if(min > 0) min = 0;
5748 if(max < 0) max = 0;
5750 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5752 // case 'width': width = height * ratio; break;
5754 // default: height = width / ratio; break;
5756 //state.element.style.width = width.toString() + 'px';
5757 //state.element.style.height = height.toString() + 'px';
5762 lines: 12, // The number of lines to draw
5763 angle: 0.15, // The span of the gauge arc
5764 lineWidth: 0.50, // The line thickness
5765 radiusScale: 0.85, // Relative radius
5767 length: 0.8, // 0.9 The radius of the inner circle
5768 strokeWidth: 0.035, // The rotation offset
5769 color: pointerColor // Fill color
5771 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5772 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5773 colorStart: startColor, // Colors
5774 colorStop: stopColor, // just experiment with them
5775 strokeColor: strokeColor, // to see which ones work best for you
5776 generateGradient: (generateGradient === true),
5778 highDpiSupport: true // High resolution support
5781 if (generateGradient.constructor === Array) {
5783 // data-gauge-generate-gradient="[0, 50, 100]"
5784 // data-gauge-gradient-percent-color-0="#FFFFFF"
5785 // data-gauge-gradient-percent-color-50="#999900"
5786 // data-gauge-gradient-percent-color-100="#000000"
5788 options.percentColors = [];
5789 var len = generateGradient.length;
5791 var pcent = generateGradient[len];
5792 var color = self.attr('data-gauge-gradient-percent-color-' + pcent.toString()) || false;
5793 if(color !== false) {
5797 options.percentColors.unshift(a);
5800 if(options.percentColors.length === 0)
5801 delete options.percentColors;
5803 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5804 //noinspection PointlessArithmeticExpressionJS
5805 options.percentColors = [
5806 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5807 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5808 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5809 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5810 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5811 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5812 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5813 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5814 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5815 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5816 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5819 state.gauge_canvas = document.createElement('canvas');
5820 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5821 state.gauge_canvas.className = 'gaugeChart';
5822 state.gauge_canvas.width = width;
5823 state.gauge_canvas.height = height;
5824 state.element_chart.appendChild(state.gauge_canvas);
5826 var valuefontsize = Math.floor(height / 6);
5827 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5828 state.gaugeChartLabel = document.createElement('span');
5829 state.gaugeChartLabel.className = 'gaugeChartLabel';
5830 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5831 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5832 state.element_chart.appendChild(state.gaugeChartLabel);
5834 var titlefontsize = Math.round(valuefontsize / 2);
5836 state.gaugeChartTitle = document.createElement('span');
5837 state.gaugeChartTitle.className = 'gaugeChartTitle';
5838 state.gaugeChartTitle.innerText = state.title;
5839 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5840 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5841 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5842 state.element_chart.appendChild(state.gaugeChartTitle);
5844 var unitfontsize = Math.round(titlefontsize * 0.9);
5845 state.gaugeChartUnits = document.createElement('span');
5846 state.gaugeChartUnits.className = 'gaugeChartUnits';
5847 state.gaugeChartUnits.innerText = state.units;
5848 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5849 state.element_chart.appendChild(state.gaugeChartUnits);
5851 state.gaugeChartMin = document.createElement('span');
5852 state.gaugeChartMin.className = 'gaugeChartMin';
5853 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5854 state.element_chart.appendChild(state.gaugeChartMin);
5856 state.gaugeChartMax = document.createElement('span');
5857 state.gaugeChartMax.className = 'gaugeChartMax';
5858 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5859 state.element_chart.appendChild(state.gaugeChartMax);
5861 // when we just re-create the chart
5862 // do not animate the first update
5864 if(typeof state.gauge_instance !== 'undefined')
5867 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5869 state.___gaugeOld__ = {
5878 // we will always feed a percentage
5879 state.gauge_instance.minValue = 0;
5880 state.gauge_instance.maxValue = 100;
5882 NETDATA.gaugeAnimation(state, animate);
5883 NETDATA.gaugeSet(state, value, min, max);
5884 NETDATA.gaugeSetLabels(state, value, min, max);
5885 NETDATA.gaugeAnimation(state, true);
5889 // ----------------------------------------------------------------------------------------------------------------
5890 // Charts Libraries Registration
5892 NETDATA.chartLibraries = {
5894 initialize: NETDATA.dygraphInitialize,
5895 create: NETDATA.dygraphChartCreate,
5896 update: NETDATA.dygraphChartUpdate,
5897 resize: function(state) {
5898 if(typeof state.dygraph_instance.resize === 'function')
5899 state.dygraph_instance.resize();
5901 setSelection: NETDATA.dygraphSetSelection,
5902 clearSelection: NETDATA.dygraphClearSelection,
5903 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5906 format: function(state) { void(state); return 'json'; },
5907 options: function(state) { void(state); return 'ms|flip'; },
5908 legend: function(state) {
5909 return (this.isSparkline(state) === false)?'right-side':null;
5911 autoresize: function(state) { void(state); return true; },
5912 max_updates_to_recreate: function(state) { void(state); return 5000; },
5913 track_colors: function(state) { void(state); return true; },
5914 pixels_per_point: function(state) {
5915 return (this.isSparkline(state) === false)?3:2;
5917 isSparkline: function(state) {
5918 if(typeof state.dygraph_sparkline === 'undefined') {
5919 var t = $(state.element).data('dygraph-theme');
5920 state.dygraph_sparkline = (t === 'sparkline');
5922 return state.dygraph_sparkline;
5926 initialize: NETDATA.sparklineInitialize,
5927 create: NETDATA.sparklineChartCreate,
5928 update: NETDATA.sparklineChartUpdate,
5930 setSelection: undefined, // function(state, t) { void(state); return true; },
5931 clearSelection: undefined, // function(state) { void(state); return true; },
5932 toolboxPanAndZoom: null,
5935 format: function(state) { void(state); return 'array'; },
5936 options: function(state) { void(state); return 'flip|abs'; },
5937 legend: function(state) { void(state); return null; },
5938 autoresize: function(state) { void(state); return false; },
5939 max_updates_to_recreate: function(state) { void(state); return 5000; },
5940 track_colors: function(state) { void(state); return false; },
5941 pixels_per_point: function(state) { void(state); return 3; }
5944 initialize: NETDATA.peityInitialize,
5945 create: NETDATA.peityChartCreate,
5946 update: NETDATA.peityChartUpdate,
5948 setSelection: undefined, // function(state, t) { void(state); return true; },
5949 clearSelection: undefined, // function(state) { void(state); return true; },
5950 toolboxPanAndZoom: null,
5953 format: function(state) { void(state); return 'ssvcomma'; },
5954 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5955 legend: function(state) { void(state); return null; },
5956 autoresize: function(state) { void(state); return false; },
5957 max_updates_to_recreate: function(state) { void(state); return 5000; },
5958 track_colors: function(state) { void(state); return false; },
5959 pixels_per_point: function(state) { void(state); return 3; }
5962 initialize: NETDATA.morrisInitialize,
5963 create: NETDATA.morrisChartCreate,
5964 update: NETDATA.morrisChartUpdate,
5966 setSelection: undefined, // function(state, t) { void(state); return true; },
5967 clearSelection: undefined, // function(state) { void(state); return true; },
5968 toolboxPanAndZoom: null,
5971 format: function(state) { void(state); return 'json'; },
5972 options: function(state) { void(state); return 'objectrows|ms'; },
5973 legend: function(state) { void(state); return null; },
5974 autoresize: function(state) { void(state); return false; },
5975 max_updates_to_recreate: function(state) { void(state); return 50; },
5976 track_colors: function(state) { void(state); return false; },
5977 pixels_per_point: function(state) { void(state); return 15; }
5980 initialize: NETDATA.googleInitialize,
5981 create: NETDATA.googleChartCreate,
5982 update: NETDATA.googleChartUpdate,
5984 setSelection: undefined, //function(state, t) { void(state); return true; },
5985 clearSelection: undefined, //function(state) { void(state); return true; },
5986 toolboxPanAndZoom: null,
5989 format: function(state) { void(state); return 'datatable'; },
5990 options: function(state) { void(state); return ''; },
5991 legend: function(state) { void(state); return null; },
5992 autoresize: function(state) { void(state); return false; },
5993 max_updates_to_recreate: function(state) { void(state); return 300; },
5994 track_colors: function(state) { void(state); return false; },
5995 pixels_per_point: function(state) { void(state); return 4; }
5998 initialize: NETDATA.raphaelInitialize,
5999 create: NETDATA.raphaelChartCreate,
6000 update: NETDATA.raphaelChartUpdate,
6002 setSelection: undefined, // function(state, t) { void(state); return true; },
6003 clearSelection: undefined, // function(state) { void(state); return true; },
6004 toolboxPanAndZoom: null,
6007 format: function(state) { void(state); return 'json'; },
6008 options: function(state) { void(state); return ''; },
6009 legend: function(state) { void(state); return null; },
6010 autoresize: function(state) { void(state); return false; },
6011 max_updates_to_recreate: function(state) { void(state); return 5000; },
6012 track_colors: function(state) { void(state); return false; },
6013 pixels_per_point: function(state) { void(state); return 3; }
6016 initialize: NETDATA.c3Initialize,
6017 create: NETDATA.c3ChartCreate,
6018 update: NETDATA.c3ChartUpdate,
6020 setSelection: undefined, // function(state, t) { void(state); return true; },
6021 clearSelection: undefined, // function(state) { void(state); return true; },
6022 toolboxPanAndZoom: null,
6025 format: function(state) { void(state); return 'csvjsonarray'; },
6026 options: function(state) { void(state); return 'milliseconds'; },
6027 legend: function(state) { void(state); return null; },
6028 autoresize: function(state) { void(state); return false; },
6029 max_updates_to_recreate: function(state) { void(state); return 5000; },
6030 track_colors: function(state) { void(state); return false; },
6031 pixels_per_point: function(state) { void(state); return 15; }
6034 initialize: NETDATA.d3Initialize,
6035 create: NETDATA.d3ChartCreate,
6036 update: NETDATA.d3ChartUpdate,
6038 setSelection: undefined, // function(state, t) { void(state); return true; },
6039 clearSelection: undefined, // function(state) { void(state); return true; },
6040 toolboxPanAndZoom: null,
6043 format: function(state) { void(state); return 'json'; },
6044 options: function(state) { void(state); return ''; },
6045 legend: function(state) { void(state); return null; },
6046 autoresize: function(state) { void(state); return false; },
6047 max_updates_to_recreate: function(state) { void(state); return 5000; },
6048 track_colors: function(state) { void(state); return false; },
6049 pixels_per_point: function(state) { void(state); return 3; }
6052 initialize: NETDATA.easypiechartInitialize,
6053 create: NETDATA.easypiechartChartCreate,
6054 update: NETDATA.easypiechartChartUpdate,
6056 setSelection: NETDATA.easypiechartSetSelection,
6057 clearSelection: NETDATA.easypiechartClearSelection,
6058 toolboxPanAndZoom: null,
6061 format: function(state) { void(state); return 'array'; },
6062 options: function(state) { void(state); return 'absolute'; },
6063 legend: function(state) { void(state); return null; },
6064 autoresize: function(state) { void(state); return false; },
6065 max_updates_to_recreate: function(state) { void(state); return 5000; },
6066 track_colors: function(state) { void(state); return true; },
6067 pixels_per_point: function(state) { void(state); return 3; },
6071 initialize: NETDATA.gaugeInitialize,
6072 create: NETDATA.gaugeChartCreate,
6073 update: NETDATA.gaugeChartUpdate,
6075 setSelection: NETDATA.gaugeSetSelection,
6076 clearSelection: NETDATA.gaugeClearSelection,
6077 toolboxPanAndZoom: null,
6080 format: function(state) { void(state); return 'array'; },
6081 options: function(state) { void(state); return 'absolute'; },
6082 legend: function(state) { void(state); return null; },
6083 autoresize: function(state) { void(state); return false; },
6084 max_updates_to_recreate: function(state) { void(state); return 5000; },
6085 track_colors: function(state) { void(state); return true; },
6086 pixels_per_point: function(state) { void(state); return 3; },
6091 NETDATA.registerChartLibrary = function(library, url) {
6092 if(NETDATA.options.debug.libraries === true)
6093 console.log("registering chart library: " + library);
6095 NETDATA.chartLibraries[library].url = url;
6096 NETDATA.chartLibraries[library].initialized = true;
6097 NETDATA.chartLibraries[library].enabled = true;
6100 // ----------------------------------------------------------------------------------------------------------------
6101 // Load required JS libraries and CSS
6103 NETDATA.requiredJs = [
6105 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6107 isAlreadyLoaded: function() {
6108 // check if bootstrap is loaded
6109 if(typeof $().emulateTransitionEnd === 'function')
6112 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6117 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6118 isAlreadyLoaded: function() { return false; }
6122 NETDATA.requiredCSS = [
6124 url: NETDATA.themes.current.bootstrap_css,
6125 isAlreadyLoaded: function() {
6126 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6130 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6131 isAlreadyLoaded: function() { return false; }
6134 url: NETDATA.themes.current.dashboard_css,
6135 isAlreadyLoaded: function() { return false; }
6139 NETDATA.loadedRequiredJs = 0;
6140 NETDATA.loadRequiredJs = function(index, callback) {
6141 if(index >= NETDATA.requiredJs.length) {
6142 if(typeof callback === 'function')
6147 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6148 NETDATA.loadedRequiredJs++;
6149 NETDATA.loadRequiredJs(++index, callback);
6153 if(NETDATA.options.debug.main_loop === true)
6154 console.log('loading ' + NETDATA.requiredJs[index].url);
6157 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6161 url: NETDATA.requiredJs[index].url,
6164 xhrFields: { withCredentials: true } // required for the cookie
6167 if(NETDATA.options.debug.main_loop === true)
6168 console.log('loaded ' + NETDATA.requiredJs[index].url);
6171 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6173 .always(function() {
6174 NETDATA.loadedRequiredJs++;
6177 NETDATA.loadRequiredJs(++index, callback);
6181 NETDATA.loadRequiredJs(++index, callback);
6184 NETDATA.loadRequiredCSS = function(index) {
6185 if(index >= NETDATA.requiredCSS.length)
6188 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6189 NETDATA.loadRequiredCSS(++index);
6193 if(NETDATA.options.debug.main_loop === true)
6194 console.log('loading ' + NETDATA.requiredCSS[index].url);
6196 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6197 NETDATA.loadRequiredCSS(++index);
6201 // ----------------------------------------------------------------------------------------------------------------
6202 // Registry of netdata hosts
6205 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6206 chart_div_offset: 100, // give that space above the chart when scrolling to it
6207 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6208 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6210 ms_penalty: 0, // the time penalty of the next alarm
6211 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6212 // if alarms are shown faster than: one per 500ms
6214 notifications: false, // when true, the browser supports notifications (may not be granted though)
6215 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6216 first_notification_id: 0, // the id of the first alarm_log entry for this session
6217 // this is used to prevent CLEAR notifications for past events
6218 // notifications_shown: [],
6220 server: null, // the server to connect to for fetching alarms
6221 current: null, // the list of raised alarms - updated in the background
6222 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6224 notify: function(entry) {
6225 // console.log('alarm ' + entry.unique_id);
6227 if(entry.updated === true) {
6228 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6232 var value_string = entry.value_string;
6234 if(NETDATA.alarms.current !== null) {
6235 // get the current value_string
6236 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6237 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6238 value_string = t.value_string;
6241 var name = entry.name.replace(/_/g, ' ');
6242 var status = entry.status.toLowerCase();
6243 var title = name + ' = ' + value_string.toString();
6244 var tag = entry.alarm_id;
6245 var icon = 'images/seo-performance-128.png';
6246 var interaction = false;
6250 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6252 switch(entry.status) {
6260 case 'UNINITIALIZED':
6264 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6265 // console.log('alarm ' + entry.unique_id + ' is not current');
6268 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6269 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6272 if(entry.no_clear_notification === true) {
6273 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6276 title = name + ' back to normal (' + value_string.toString() + ')';
6277 icon = 'images/check-mark-2-128-green.png';
6278 interaction = false;
6282 if(entry.old_status === 'CRITICAL')
6283 status = 'demoted to ' + entry.status.toLowerCase();
6285 icon = 'images/alert-128-orange.png';
6286 interaction = false;
6290 if(entry.old_status === 'WARNING')
6291 status = 'escalated to ' + entry.status.toLowerCase();
6293 icon = 'images/alert-128-red.png';
6298 console.log('invalid alarm status ' + entry.status);
6303 // cleanup old notifications with the same alarm_id as this one
6304 // FIXME: it does not seem to work on any web browser!
6305 var len = NETDATA.alarms.notifications_shown.length;
6307 var n = NETDATA.alarms.notifications_shown[len];
6308 if(n.data.alarm_id === entry.alarm_id) {
6309 console.log('removing old alarm ' + n.data.unique_id);
6311 // close the notification
6314 // remove it from the array
6315 NETDATA.alarms.notifications_shown.splice(len, 1);
6316 len = NETDATA.alarms.notifications_shown.length;
6323 setTimeout(function() {
6324 // show this notification
6325 // console.log('new notification: ' + title);
6326 var n = new Notification(title, {
6327 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6329 requireInteraction: interaction,
6330 icon: NETDATA.serverDefault + icon,
6334 n.onclick = function(event) {
6335 event.preventDefault();
6336 NETDATA.alarms.onclick(event.target.data);
6340 // NETDATA.alarms.notifications_shown.push(n);
6341 // console.log(entry);
6342 }, NETDATA.alarms.ms_penalty);
6344 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6348 scrollToChart: function(chart_id) {
6349 if(typeof chart_id === 'string') {
6350 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6351 if(typeof offset !== 'undefined') {
6352 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6359 scrollToAlarm: function(alarm) {
6360 if(typeof alarm === 'object') {
6361 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6363 if(ret === true && NETDATA.options.page_is_visible === false)
6365 // 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.');
6370 notifyAll: function() {
6371 // console.log('FETCHING ALARM LOG');
6372 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6373 // console.log('ALARM LOG FETCHED');
6375 if(data === null || typeof data !== 'object') {
6376 console.log('invalid alarms log response');
6380 if(data.length === 0) {
6381 console.log('received empty alarm log');
6385 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6387 data.sort(function(a, b) {
6388 if(a.unique_id > b.unique_id) return -1;
6389 if(a.unique_id < b.unique_id) return 1;
6393 NETDATA.alarms.ms_penalty = 0;
6395 var len = data.length;
6397 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6398 NETDATA.alarms.notify(data[len]);
6401 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6404 NETDATA.alarms.last_notification_id = data[0].unique_id;
6405 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6406 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6410 check_notifications: function() {
6411 // returns true if we should fire 1+ notifications
6413 if(NETDATA.alarms.notifications !== true) {
6414 // console.log('notifications not available');
6418 if(Notification.permission !== 'granted') {
6419 // console.log('notifications not granted');
6423 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6424 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6426 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6427 // console.log('new alarms detected');
6430 //else console.log('no new alarms');
6432 // else console.log('cannot process alarms');
6437 get: function(what, callback) {
6439 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6443 'Cache-Control': 'no-cache, no-store',
6444 'Pragma': 'no-cache'
6446 xhrFields: { withCredentials: true } // required for the cookie
6448 .done(function(data) {
6449 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6450 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6452 if(typeof callback === 'function')
6453 return callback(data);
6456 NETDATA.error(415, NETDATA.alarms.server);
6458 if(typeof callback === 'function')
6459 return callback(null);
6463 update_forever: function() {
6464 NETDATA.alarms.get('active', function(data) {
6466 NETDATA.alarms.current = data;
6468 if(NETDATA.alarms.check_notifications() === true) {
6469 NETDATA.alarms.notifyAll();
6472 if (typeof NETDATA.alarms.callback === 'function') {
6473 NETDATA.alarms.callback(data);
6476 // Health monitoring is disabled on this netdata
6477 if(data.status === false) return;
6480 setTimeout(NETDATA.alarms.update_forever, 10000);
6484 get_log: function(last_id, callback) {
6485 // console.log('fetching all log after ' + last_id.toString());
6487 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6491 'Cache-Control': 'no-cache, no-store',
6492 'Pragma': 'no-cache'
6494 xhrFields: { withCredentials: true } // required for the cookie
6496 .done(function(data) {
6497 if(typeof callback === 'function')
6498 return callback(data);
6501 NETDATA.error(416, NETDATA.alarms.server);
6503 if(typeof callback === 'function')
6504 return callback(null);
6509 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6511 NETDATA.alarms.last_notification_id =
6512 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6514 if(NETDATA.alarms.onclick === null)
6515 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6517 if(netdataShowAlarms === true) {
6518 NETDATA.alarms.update_forever();
6520 if('Notification' in window) {
6521 // console.log('notifications available');
6522 NETDATA.alarms.notifications = true;
6524 if(Notification.permission === 'default')
6525 Notification.requestPermission();
6531 // ----------------------------------------------------------------------------------------------------------------
6532 // Registry of netdata hosts
6534 NETDATA.registry = {
6535 server: null, // the netdata registry server
6536 person_guid: null, // the unique ID of this browser / user
6537 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6538 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6539 machines: null, // the user's other URLs
6540 machines_array: null, // the user's other URLs in an array
6543 parsePersonUrls: function(person_urls) {
6544 // console.log(person_urls);
6545 NETDATA.registry.person_urls = person_urls;
6548 NETDATA.registry.machines = {};
6549 NETDATA.registry.machines_array = [];
6551 var apu = person_urls;
6554 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6555 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6561 accesses: apu[i][3],
6565 obj.alternate_urls.push(apu[i][1]);
6567 NETDATA.registry.machines[apu[i][0]] = obj;
6568 NETDATA.registry.machines_array.push(obj);
6571 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6573 var pu = NETDATA.registry.machines[apu[i][0]];
6574 if(pu.last_t < apu[i][2]) {
6576 pu.last_t = apu[i][2];
6577 pu.name = apu[i][4];
6579 pu.accesses += apu[i][3];
6580 pu.alternate_urls.push(apu[i][1]);
6585 if(typeof netdataRegistryCallback === 'function')
6586 netdataRegistryCallback(NETDATA.registry.machines_array);
6590 if(netdataRegistry !== true) return;
6592 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6594 NETDATA.registry.server = data.registry;
6595 NETDATA.registry.machine_guid = data.machine_guid;
6596 NETDATA.registry.hostname = data.hostname;
6598 NETDATA.registry.access(2, function (person_urls) {
6599 NETDATA.registry.parsePersonUrls(person_urls);
6606 hello: function(host, callback) {
6607 host = NETDATA.fixHost(host);
6609 // send HELLO to a netdata server:
6610 // 1. verifies the server is reachable
6611 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6613 url: host + '/api/v1/registry?action=hello',
6617 'Cache-Control': 'no-cache, no-store',
6618 'Pragma': 'no-cache'
6620 xhrFields: { withCredentials: true } // required for the cookie
6622 .done(function(data) {
6623 if(typeof data.status !== 'string' || data.status !== 'ok') {
6624 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6628 if(typeof callback === 'function')
6629 return callback(data);
6632 NETDATA.error(407, host);
6634 if(typeof callback === 'function')
6635 return callback(null);
6639 access: function(max_redirects, callback) {
6640 // send ACCESS to a netdata registry:
6641 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6642 // 2. it responds with a list of netdata servers we know
6643 // the registry identifies us using a cookie it sets the first time we access it
6644 // the registry may respond with a redirect URL to send us to another registry
6646 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),
6650 'Cache-Control': 'no-cache, no-store',
6651 'Pragma': 'no-cache'
6653 xhrFields: { withCredentials: true } // required for the cookie
6655 .done(function(data) {
6656 var redirect = null;
6657 if(typeof data.registry === 'string')
6658 redirect = data.registry;
6660 if(typeof data.status !== 'string' || data.status !== 'ok') {
6661 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6666 if(redirect !== null && max_redirects > 0) {
6667 NETDATA.registry.server = redirect;
6668 NETDATA.registry.access(max_redirects - 1, callback);
6671 if(typeof callback === 'function')
6672 return callback(null);
6676 if(typeof data.person_guid === 'string')
6677 NETDATA.registry.person_guid = data.person_guid;
6679 if(typeof callback === 'function')
6680 return callback(data.urls);
6684 NETDATA.error(410, NETDATA.registry.server);
6686 if(typeof callback === 'function')
6687 return callback(null);
6691 delete: function(delete_url, callback) {
6692 // send DELETE to a netdata registry:
6694 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),
6698 'Cache-Control': 'no-cache, no-store',
6699 'Pragma': 'no-cache'
6701 xhrFields: { withCredentials: true } // required for the cookie
6703 .done(function(data) {
6704 if(typeof data.status !== 'string' || data.status !== 'ok') {
6705 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6709 if(typeof callback === 'function')
6710 return callback(data);
6713 NETDATA.error(412, NETDATA.registry.server);
6715 if(typeof callback === 'function')
6716 return callback(null);
6720 search: function(machine_guid, callback) {
6721 // SEARCH for the URLs of a machine:
6723 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,
6727 'Cache-Control': 'no-cache, no-store',
6728 'Pragma': 'no-cache'
6730 xhrFields: { withCredentials: true } // required for the cookie
6732 .done(function(data) {
6733 if(typeof data.status !== 'string' || data.status !== 'ok') {
6734 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6738 if(typeof callback === 'function')
6739 return callback(data);
6742 NETDATA.error(418, NETDATA.registry.server);
6744 if(typeof callback === 'function')
6745 return callback(null);
6749 switch: function(new_person_guid, callback) {
6752 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,
6756 'Cache-Control': 'no-cache, no-store',
6757 'Pragma': 'no-cache'
6759 xhrFields: { withCredentials: true } // required for the cookie
6761 .done(function(data) {
6762 if(typeof data.status !== 'string' || data.status !== 'ok') {
6763 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6767 if(typeof callback === 'function')
6768 return callback(data);
6771 NETDATA.error(414, NETDATA.registry.server);
6773 if(typeof callback === 'function')
6774 return callback(null);
6779 // ----------------------------------------------------------------------------------------------------------------
6782 if(typeof netdataPrepCallback === 'function')
6783 netdataPrepCallback();
6785 NETDATA.errorReset();
6786 NETDATA.loadRequiredCSS(0);
6788 NETDATA._loadjQuery(function() {
6789 NETDATA.loadRequiredJs(0, function() {
6790 if(typeof $().emulateTransitionEnd !== 'function') {
6791 // bootstrap is not available
6792 NETDATA.options.current.show_help = false;
6795 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6796 if(NETDATA.options.debug.main_loop === true)
6797 console.log('starting chart refresh thread');
6803 })(window, document);