1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
4 /*global netdataNoDygraphs *//* boolean, disable dygraph charts
6 /*global netdataNoSparklines *//* boolean, disable sparkline charts
8 /*global netdataNoPeitys *//* boolean, disable peity charts
10 /*global netdataNoGoogleCharts *//* boolean, disable google charts
12 /*global netdataNoMorris *//* boolean, disable morris charts
14 /*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
16 /*global netdataNoGauge *//* boolean, disable gauge.js charts
18 /*global netdataNoD3 *//* boolean, disable d3 charts
20 /*global netdataNoC3 *//* boolean, disable c3 charts
22 /*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
24 /*global netdataDontStart *//* boolean, do not start the thread to process the charts
26 /*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
28 /*global netdataRegistry:true *//* boolean, use the netdata registry
30 /*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
31 * (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
34 /*global netdataShowHelp:true *//* boolean, disable charts help
36 /*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
40 /*global netdataCallback *//* function, callback to be called when netdata is ready to start
42 * netdata will be running while this is called
43 * (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
46 /*global netdataServer *//* string, the URL of the netdata server to use
47 * (default: the URL the page is hosted at) */
49 // ----------------------------------------------------------------------------
52 var NETDATA = window.NETDATA || {};
54 (function(window, document) {
55 // ------------------------------------------------------------------------
56 // compatibility fixes
58 // fix IE issue with console
59 if(!window.console) { window.console = { log: function(){} }; }
61 // if string.endsWith is not defined, define it
62 if(typeof String.prototype.endsWith !== 'function') {
63 String.prototype.endsWith = function(s) {
64 if(s.length > this.length) return false;
65 return this.slice(-s.length) === s;
69 // if string.startsWith is not defined, define it
70 if(typeof String.prototype.startsWith !== 'function') {
71 String.prototype.startsWith = function(s) {
72 if(s.length > this.length) return false;
73 return this.slice(s.length) === s;
77 NETDATA.name2id = function(s) {
86 // ----------------------------------------------------------------------------------------------------------------
87 // Detect the netdata server
89 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91 NETDATA._scriptSource = function() {
94 if(typeof document.currentScript !== 'undefined') {
95 script = document.currentScript;
98 var all_scripts = document.getElementsByTagName('script');
99 script = all_scripts[all_scripts.length - 1];
102 if (typeof script.getAttribute.length !== 'undefined')
105 script = script.getAttribute('src', -1);
110 if(typeof netdataServer !== 'undefined')
111 NETDATA.serverDefault = netdataServer;
113 var s = NETDATA._scriptSource();
114 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
116 console.log('WARNING: Cannot detect the URL of the netdata server.');
117 NETDATA.serverDefault = null;
121 if(NETDATA.serverDefault === null)
122 NETDATA.serverDefault = '';
123 else if(NETDATA.serverDefault.slice(-1) !== '/')
124 NETDATA.serverDefault += '/';
126 // default URLs for all the external files we need
127 // make them RELATIVE so that the whole thing can also be
128 // installed under a web server
129 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-1.3.1.min.js';
134 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
135 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
136 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
137 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
138 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
139 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
140 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
141 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
142 NETDATA.google_js = 'https://www.google.com/jsapi';
146 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
147 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
148 background: '#FFFFFF',
149 foreground: '#000000',
152 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
153 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
154 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
155 '#329262', '#3B3EAC' ],
156 easypiechart_track: '#f0f0f0',
157 easypiechart_scale: '#dfe0e0',
158 gauge_pointer: '#C0C0C0',
159 gauge_stroke: '#F0F0F0',
160 gauge_gradient: false
163 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
164 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
165 background: '#272b30',
166 foreground: '#C8C8C8',
169 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
170 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
171 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
172 '#a6a479', '#a66da8' ],
174 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
175 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
176 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
177 '#329262', '#3B3EFF' ],
178 easypiechart_track: '#373b40',
179 easypiechart_scale: '#373b40',
180 gauge_pointer: '#474b50',
181 gauge_stroke: '#373b40',
182 gauge_gradient: false
186 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
187 NETDATA.themes.current = NETDATA.themes[netdataTheme];
189 NETDATA.themes.current = NETDATA.themes.white;
191 NETDATA.colors = NETDATA.themes.current.colors;
193 // these are the colors Google Charts are using
194 // we have them here to attempt emulate their look and feel on the other chart libraries
195 // http://there4.io/2012/05/02/google-chart-color-list/
196 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
197 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
198 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
200 // an alternative set
201 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
202 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
203 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
205 if(typeof netdataShowHelp === 'undefined')
206 netdataShowHelp = true;
208 if(typeof netdataShowAlarms === 'undefined')
209 netdataShowAlarms = false;
211 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
212 netdataRegistryAfterMs = 1500;
214 if(typeof netdataRegistry === 'undefined') {
215 // backward compatibility
216 netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
218 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
219 netdataRegistry = true;
222 // ----------------------------------------------------------------------------------------------------------------
223 // detect if this is probably a slow device
225 var isSlowDeviceResult = undefined;
226 var isSlowDevice = function() {
227 if(isSlowDeviceResult !== undefined)
228 return isSlowDeviceResult;
231 var ua = navigator.userAgent.toLowerCase();
233 var iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
234 var android = /android/.test(ua) && !window.MSStream;
235 isSlowDeviceResult = (iOS === true || android === true);
238 isSlowDeviceResult = false;
241 return isSlowDeviceResult;
244 // ----------------------------------------------------------------------------------------------------------------
245 // the defaults for all charts
247 // if the user does not specify any of these, the following will be used
249 NETDATA.chartDefaults = {
250 host: NETDATA.serverDefault, // the server to get data from
251 width: '100%', // the chart width - can be null
252 height: '100%', // the chart height - can be null
253 min_width: null, // the chart minimum width - can be null
254 library: 'dygraph', // the graphing library to use
255 method: 'average', // the grouping method
256 before: 0, // panning
257 after: -600, // panning
258 pixels_per_point: 1, // the detail of the chart
259 fill_luminance: 0.8 // luminance of colors in solid areas
262 // ----------------------------------------------------------------------------------------------------------------
266 pauseCallback: null, // a callback when we are really paused
268 pause: false, // when enabled we don't auto-refresh the charts
270 targets: null, // an array of all the state objects that are
271 // currently active (independently of their
272 // viewport visibility)
274 updated_dom: true, // when true, the DOM has been updated with
275 // new elements we have to check.
277 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
278 // rendering charts continuously.
279 // used with .current.fast_render_timeframe
281 page_is_visible: true, // when true, this page is visible
283 auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
284 // auto-refresher for some time (when a chart is
285 // performing pan or zoom, we need to stop refreshing
286 // all other charts, to have the maximum speed for
287 // rendering the chart that is panned or zoomed).
288 // Used with .current.global_pan_sync_time
290 last_resized: Date.now(), // the timestamp of the last resize request
292 last_page_scroll: 0, // the timestamp the last time the page was scrolled
294 // the current profile
295 // we may have many...
297 pixels_per_point: isSlowDevice()?5:1, // the minimum pixels per point for all charts
298 // increase this to speed javascript up
299 // each chart library has its own limit too
300 // the max of this and the chart library is used
301 // the final is calculated every time, so a change
302 // here will have immediate effect on the next chart
305 idle_between_charts: 100, // ms - how much time to wait between chart updates
307 fast_render_timeframe: 200, // ms - render continuously until this time of continuous
308 // rendering has been reached
309 // this setting is used to make it render e.g. 10
310 // charts at once, sleep idle_between_charts time
311 // and continue for another 10 charts.
313 idle_between_loops: 500, // ms - if all charts have been updated, wait this
314 // time before starting again.
316 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
318 idle_lost_focus: 500, // ms - when the window does not have focus, check
319 // if focus has been regained, every this time
321 global_pan_sync_time: 1000, // ms - when you pan or zoom a chart, the background
322 // auto-refreshing of charts is paused for this amount
325 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
326 // of time before setting up synchronized selections
329 sync_selection: true, // enable or disable selection sync
331 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
333 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
335 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
337 update_only_visible: true, // enable or disable visibility management
339 parallel_refresher: (isSlowDevice() === false), // enable parallel refresh of charts
341 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
343 destroy_on_hide: (isSlowDevice() === true), // destroy charts when they are not visible
345 show_help: netdataShowHelp, // when enabled the charts will show some help
346 show_help_delay_show_ms: 500,
347 show_help_delay_hide_ms: 0,
349 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
351 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
352 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
354 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
356 smooth_plot: (isSlowDevice() === false), // enable smooth plot, where possible
358 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
360 color_fill_opacity_line: 1.0,
361 color_fill_opacity_area: 0.2,
362 color_fill_opacity_stacked: 0.8,
364 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
365 pan_and_zoom_factor_multiplier_control: 2.0,
366 pan_and_zoom_factor_multiplier_shift: 3.0,
367 pan_and_zoom_factor_multiplier_alt: 4.0,
369 abort_ajax_on_scroll: false, // kill pending ajax page scroll
370 async_on_scroll: false, // sync/async onscroll handler
371 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
373 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
375 setOptionCallback: function() { }
383 chart_data_url: false,
384 chart_errors: false, // FIXME: remember to set it to false before merging
392 NETDATA.statistics = {
395 refreshes_active_max: 0
399 // ----------------------------------------------------------------------------------------------------------------
400 // local storage options
402 NETDATA.localStorage = {
405 callback: {} // only used for resetting back to defaults
408 NETDATA.localStorageTested = -1;
409 NETDATA.localStorageTest = function() {
410 if(NETDATA.localStorageTested !== -1)
411 return NETDATA.localStorageTested;
413 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
416 localStorage.setItem(test, test);
417 localStorage.removeItem(test);
418 NETDATA.localStorageTested = true;
421 NETDATA.localStorageTested = false;
425 NETDATA.localStorageTested = false;
427 return NETDATA.localStorageTested;
430 NETDATA.localStorageGet = function(key, def, callback) {
433 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
434 NETDATA.localStorage.default[key.toString()] = def;
435 NETDATA.localStorage.callback[key.toString()] = callback;
438 if(NETDATA.localStorageTest() === true) {
440 // console.log('localStorage: loading "' + key.toString() + '"');
441 ret = localStorage.getItem(key.toString());
442 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
443 if(ret === null || ret === 'undefined') {
444 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
445 localStorage.setItem(key.toString(), JSON.stringify(def));
449 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
450 ret = JSON.parse(ret);
451 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
455 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
460 if(typeof ret === 'undefined' || ret === 'undefined') {
461 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
465 NETDATA.localStorage.current[key.toString()] = ret;
469 NETDATA.localStorageSet = function(key, value, callback) {
470 if(typeof value === 'undefined' || value === 'undefined') {
471 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
474 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
475 NETDATA.localStorage.default[key.toString()] = value;
476 NETDATA.localStorage.current[key.toString()] = value;
477 NETDATA.localStorage.callback[key.toString()] = callback;
480 if(NETDATA.localStorageTest() === true) {
481 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
483 localStorage.setItem(key.toString(), JSON.stringify(value));
486 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
490 NETDATA.localStorage.current[key.toString()] = value;
494 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
495 var keys = Object.keys(obj);
496 var len = keys.length;
500 if(typeof obj[i] === 'object') {
501 //console.log('object ' + prefix + '.' + i.toString());
502 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
506 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
510 NETDATA.setOption = function(key, value) {
511 if(key.toString() === 'setOptionCallback') {
512 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
513 NETDATA.options.current[key.toString()] = value;
514 NETDATA.options.current.setOptionCallback();
517 else if(NETDATA.options.current[key.toString()] !== value) {
518 var name = 'options.' + key.toString();
520 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
521 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
523 //console.log(NETDATA.localStorage);
524 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
525 //console.log(NETDATA.options);
526 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
528 if(typeof NETDATA.options.current.setOptionCallback === 'function')
529 NETDATA.options.current.setOptionCallback();
535 NETDATA.getOption = function(key) {
536 return NETDATA.options.current[key.toString()];
539 // read settings from local storage
540 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
542 // always start with this option enabled.
543 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
545 NETDATA.resetOptions = function() {
546 var keys = Object.keys(NETDATA.localStorage.default);
547 var len = keys.length;
550 var a = i.split('.');
552 if(a[0] === 'options') {
553 if(a[1] === 'setOptionCallback') continue;
554 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
555 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
557 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
559 else if(a[0] === 'chart_heights') {
560 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
561 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
567 // ----------------------------------------------------------------------------------------------------------------
569 if(NETDATA.options.debug.main_loop === true)
570 console.log('welcome to NETDATA');
572 NETDATA.onresizeCallback = null;
573 NETDATA.onresize = function() {
574 NETDATA.options.last_resized = Date.now();
577 if(typeof NETDATA.onresizeCallback === 'function')
578 NETDATA.onresizeCallback();
581 NETDATA.onscroll_updater_count = 0;
582 NETDATA.onscroll_updater_running = false;
583 NETDATA.onscroll_updater_last_run = 0;
584 NETDATA.onscroll_updater_watchdog = null;
585 NETDATA.onscroll_updater_max_duration = 0;
586 NETDATA.onscroll_updater_above_threshold_count = 0;
587 NETDATA.onscroll_updater = function() {
588 NETDATA.onscroll_updater_running = true;
589 NETDATA.onscroll_updater_count++;
590 var start = Date.now();
592 var targets = NETDATA.options.targets;
593 var len = targets.length;
595 // when the user scrolls he sees that we have
596 // hidden all the not-visible charts
597 // using this little function we try to switch
598 // the charts back to visible quickly
601 if(NETDATA.options.abort_ajax_on_scroll === true) {
602 // we have to cancel pending requests too
605 if (targets[len]._updating === true) {
606 if (typeof targets[len].xhr !== 'undefined') {
607 targets[len].xhr.abort();
608 targets[len].running = false;
609 targets[len]._updating = false;
611 targets[len].isVisible();
616 // just find which chart is visible
619 targets[len].isVisible();
622 var end = Date.now();
623 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
625 if(NETDATA.options.current.async_on_scroll === false) {
626 var dt = end - start;
627 if(dt > NETDATA.onscroll_updater_max_duration) {
628 // console.log('max onscroll event handler duration increased to ' + dt);
629 NETDATA.onscroll_updater_max_duration = dt;
632 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
633 // console.log('slow: ' + dt);
634 NETDATA.onscroll_updater_above_threshold_count++;
636 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
637 NETDATA.setOption('async_on_scroll', true);
638 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
643 NETDATA.onscroll_updater_last_run = start;
644 NETDATA.onscroll_updater_running = false;
647 NETDATA.onscroll = function() {
648 // console.log('onscroll');
650 NETDATA.options.last_page_scroll = Date.now();
651 NETDATA.options.auto_refresher_stop_until = 0;
653 if(NETDATA.options.targets === null) return;
655 if(NETDATA.options.current.async_on_scroll === true) {
657 if(NETDATA.onscroll_updater_running === false) {
658 NETDATA.onscroll_updater_running = true;
659 setTimeout(NETDATA.onscroll_updater, 0);
662 if(NETDATA.onscroll_updater_watchdog !== null)
663 clearTimeout(NETDATA.onscroll_updater_watchdog);
665 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
666 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
667 // console.log('watchdog');
668 NETDATA.onscroll_updater();
671 NETDATA.onscroll_updater_watchdog = null;
677 NETDATA.onscroll_updater();
681 window.onresize = NETDATA.onresize;
682 window.onscroll = NETDATA.onscroll;
684 // ----------------------------------------------------------------------------------------------------------------
687 NETDATA.errorCodes = {
688 100: { message: "Cannot load chart library", alert: true },
689 101: { message: "Cannot load jQuery", alert: true },
690 402: { message: "Chart library not found", alert: false },
691 403: { message: "Chart library not enabled/is failed", alert: false },
692 404: { message: "Chart not found", alert: false },
693 405: { message: "Cannot download charts index from server", alert: true },
694 406: { message: "Invalid charts index downloaded from server", alert: true },
695 407: { message: "Cannot HELLO netdata server", alert: false },
696 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
697 409: { message: "Cannot ACCESS netdata registry", alert: false },
698 410: { message: "Netdata registry ACCESS failed", alert: false },
699 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
700 412: { message: "Netdata registry DELETE failed", alert: false },
701 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
702 414: { message: "Netdata registry SWITCH failed", alert: false },
703 415: { message: "Netdata alarms download failed", alert: false },
704 416: { message: "Netdata alarms log download failed", alert: false },
705 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
706 418: { message: "Netdata registry SEARCH failed", alert: false }
708 NETDATA.errorLast = {
714 NETDATA.error = function(code, msg) {
715 NETDATA.errorLast.code = code;
716 NETDATA.errorLast.message = msg;
717 NETDATA.errorLast.datetime = Date.now();
719 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
722 if(typeof netdataErrorCallback === 'function') {
723 ret = netdataErrorCallback('system', code, msg);
726 if(ret && NETDATA.errorCodes[code].alert)
727 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
730 NETDATA.errorReset = function() {
731 NETDATA.errorLast.code = 0;
732 NETDATA.errorLast.message = "You are doing fine!";
733 NETDATA.errorLast.datetime = 0;
736 // ----------------------------------------------------------------------------------------------------------------
737 // commonMin & commonMax
739 NETDATA.commonMin = {
743 get: function(state) {
744 if(typeof state.__commonMin === 'undefined') {
745 // get the commonMin setting
746 var self = $(state.element);
747 state.__commonMin = self.data('common-min') || null;
750 var min = state.data.min;
751 var name = state.__commonMin;
754 // we don't need commonMin
755 //state.log('no need for commonMin');
759 var t = this.keys[name];
760 if(typeof t === 'undefined') {
762 this.keys[name] = {};
766 var uuid = state.uuid;
767 if(typeof t[uuid] !== 'undefined') {
768 if(t[uuid] === min) {
769 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
770 return this.latest[name];
772 else if(min < this.latest[name]) {
773 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
775 this.latest[name] = min;
783 // find the common min
786 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
788 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
789 this.latest[name] = m;
794 NETDATA.commonMax = {
798 get: function(state) {
799 if(typeof state.__commonMax === 'undefined') {
800 // get the commonMax setting
801 var self = $(state.element);
802 state.__commonMax = self.data('common-max') || null;
805 var max = state.data.max;
806 var name = state.__commonMax;
809 // we don't need commonMax
810 //state.log('no need for commonMax');
814 var t = this.keys[name];
815 if(typeof t === 'undefined') {
817 this.keys[name] = {};
821 var uuid = state.uuid;
822 if(typeof t[uuid] !== 'undefined') {
823 if(t[uuid] === max) {
824 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
825 return this.latest[name];
827 else if(max > this.latest[name]) {
828 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
830 this.latest[name] = max;
838 // find the common max
841 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
843 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
844 this.latest[name] = m;
849 // ----------------------------------------------------------------------------------------------------------------
852 // When multiple charts need the same chart, we avoid downloading it
853 // multiple times (and having it in browser memory multiple time)
854 // by using this registry.
856 // Every time we download a chart definition, we save it here with .add()
857 // Then we try to get it back with .get(). If that fails, we download it.
859 NETDATA.fixHost = function(host) {
860 while(host.slice(-1) === '/')
861 host = host.substring(0, host.length - 1);
866 NETDATA.chartRegistry = {
869 fixid: function(id) {
870 return id.replace(/:/g, "_").replace(/\//g, "_");
873 add: function(host, id, data) {
874 host = this.fixid(host);
877 if(typeof this.charts[host] === 'undefined')
878 this.charts[host] = {};
880 //console.log('added ' + host + '/' + id);
881 this.charts[host][id] = data;
884 get: function(host, id) {
885 host = this.fixid(host);
888 if(typeof this.charts[host] === 'undefined')
891 if(typeof this.charts[host][id] === 'undefined')
894 //console.log('cached ' + host + '/' + id);
895 return this.charts[host][id];
898 downloadAll: function(host, callback) {
899 host = NETDATA.fixHost(host);
904 url: host + '/api/v1/charts',
907 xhrFields: { withCredentials: true } // required for the cookie
909 .done(function(data) {
911 var h = NETDATA.chartRegistry.fixid(host);
912 self.charts[h] = data.charts;
914 else NETDATA.error(406, host + '/api/v1/charts');
916 if(typeof callback === 'function')
917 return callback(data);
920 NETDATA.error(405, host + '/api/v1/charts');
922 if(typeof callback === 'function')
923 return callback(null);
928 // ----------------------------------------------------------------------------------------------------------------
929 // Global Pan and Zoom on charts
931 // Using this structure are synchronize all the charts, so that
932 // when you pan or zoom one, all others are automatically refreshed
933 // to the same timespan.
935 NETDATA.globalPanAndZoom = {
936 seq: 0, // timestamp ms
937 // every time a chart is panned or zoomed
938 // we set the timestamp here
939 // then we use it as a sequence number
940 // to find if other charts are synchronized
941 // to this time-range
943 master: null, // the master chart (state), to which all others
946 force_before_ms: null, // the timespan to sync all other charts
947 force_after_ms: null,
952 setMaster: function(state, after, before) {
953 if(NETDATA.options.current.sync_pan_and_zoom === false)
956 if(this.master !== null && this.master !== state)
957 this.master.resetChart(true, true);
959 var now = Date.now();
962 this.force_after_ms = after;
963 this.force_before_ms = before;
964 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
966 if(typeof this.callback === 'function')
967 this.callback(true, after, before);
971 clearMaster: function() {
972 if(this.master !== null) {
973 var st = this.master;
980 this.force_after_ms = null;
981 this.force_before_ms = null;
982 NETDATA.options.auto_refresher_stop_until = 0;
984 if(typeof this.callback === 'function')
985 this.callback(false, 0, 0);
988 // is the given state the master of the global
989 // pan and zoom sync?
990 isMaster: function(state) {
991 return (this.master === state);
994 // are we currently have a global pan and zoom sync?
995 isActive: function() {
996 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
999 // check if a chart, other than the master
1000 // needs to be refreshed, due to the global pan and zoom
1001 shouldBeAutoRefreshed: function(state) {
1002 if(this.master === null || this.seq === 0)
1005 //if(state.needsRecreation())
1008 return (state.tm.pan_and_zoom_seq !== this.seq);
1012 // ----------------------------------------------------------------------------------------------------------------
1013 // dimensions selection
1016 // move color assignment to dimensions, here
1018 var dimensionStatus = function(parent, label, name_div, value_div, color) {
1019 this.enabled = false;
1020 this.parent = parent;
1022 this.name_div = null;
1023 this.value_div = null;
1024 this.color = NETDATA.themes.current.foreground;
1025 this.selected = (parent.unselected_count === 0);
1027 this.setOptions(name_div, value_div, color);
1030 dimensionStatus.prototype.invalidate = function() {
1031 this.name_div = null;
1032 this.value_div = null;
1033 this.enabled = false;
1036 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1039 if(this.name_div !== name_div) {
1040 this.name_div = name_div;
1041 this.name_div.title = this.label;
1042 this.name_div.style.color = this.color;
1043 if(this.selected === false)
1044 this.name_div.className = 'netdata-legend-name not-selected';
1046 this.name_div.className = 'netdata-legend-name selected';
1049 if(this.value_div !== value_div) {
1050 this.value_div = value_div;
1051 this.value_div.title = this.label;
1052 this.value_div.style.color = this.color;
1053 if(this.selected === false)
1054 this.value_div.className = 'netdata-legend-value not-selected';
1056 this.value_div.className = 'netdata-legend-value selected';
1059 this.enabled = true;
1063 dimensionStatus.prototype.setHandler = function() {
1064 if(this.enabled === false) return;
1068 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1069 this.name_div.onclick = this.value_div.onclick = function(e) {
1071 if(ds.isSelected()) {
1073 if(e.shiftKey === true || e.ctrlKey === true) {
1074 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1077 if(ds.parent.countSelected() === 0)
1078 ds.parent.selectAll();
1081 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1082 if(ds.parent.countSelected() === 1) {
1083 ds.parent.selectAll();
1086 ds.parent.selectNone();
1092 // this is not selected
1093 if(e.shiftKey === true || e.ctrlKey === true) {
1094 // control or shift key is pressed -> select this too
1098 // no key is pressed -> select only this
1099 ds.parent.selectNone();
1104 ds.parent.state.redrawChart();
1108 dimensionStatus.prototype.select = function() {
1109 if(this.enabled === false) return;
1111 this.name_div.className = 'netdata-legend-name selected';
1112 this.value_div.className = 'netdata-legend-value selected';
1113 this.selected = true;
1116 dimensionStatus.prototype.unselect = function() {
1117 if(this.enabled === false) return;
1119 this.name_div.className = 'netdata-legend-name not-selected';
1120 this.value_div.className = 'netdata-legend-value hidden';
1121 this.selected = false;
1124 dimensionStatus.prototype.isSelected = function() {
1125 return(this.enabled === true && this.selected === true);
1128 // ----------------------------------------------------------------------------------------------------------------
1130 var dimensionsVisibility = function(state) {
1133 this.dimensions = {};
1134 this.selected_count = 0;
1135 this.unselected_count = 0;
1138 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1139 if(typeof this.dimensions[label] === 'undefined') {
1141 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1144 this.dimensions[label].setOptions(name_div, value_div, color);
1146 return this.dimensions[label];
1149 dimensionsVisibility.prototype.dimensionGet = function(label) {
1150 return this.dimensions[label];
1153 dimensionsVisibility.prototype.invalidateAll = function() {
1154 var keys = Object.keys(this.dimensions);
1155 var len = keys.length;
1157 this.dimensions[keys[len]].invalidate();
1160 dimensionsVisibility.prototype.selectAll = function() {
1161 var keys = Object.keys(this.dimensions);
1162 var len = keys.length;
1164 this.dimensions[keys[len]].select();
1167 dimensionsVisibility.prototype.countSelected = function() {
1169 var keys = Object.keys(this.dimensions);
1170 var len = keys.length;
1172 if(this.dimensions[keys[len]].isSelected()) selected++;
1177 dimensionsVisibility.prototype.selectNone = function() {
1178 var keys = Object.keys(this.dimensions);
1179 var len = keys.length;
1181 this.dimensions[keys[len]].unselect();
1184 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1186 this.selected_count = 0;
1187 this.unselected_count = 0;
1189 var len = array.length;
1191 var ds = this.dimensions[array[len]];
1192 if(typeof ds === 'undefined') {
1193 // console.log(array[i] + ' is not found');
1196 else if(ds.isSelected()) {
1198 this.selected_count++;
1202 this.unselected_count++;
1206 if(this.selected_count === 0 && this.unselected_count !== 0) {
1208 return this.selected2BooleanArray(array);
1215 // ----------------------------------------------------------------------------------------------------------------
1216 // global selection sync
1218 NETDATA.globalSelectionSync = {
1220 dont_sync_before: 0,
1225 if(this.state !== null)
1226 this.state.globalSelectionSyncStop();
1230 if(this.state !== null) {
1231 this.state.globalSelectionSyncDelay();
1236 // ----------------------------------------------------------------------------------------------------------------
1237 // Our state object, where all per-chart values are stored
1239 var chartState = function(element) {
1240 var self = $(element);
1241 this.element = element;
1244 // all private functions should use 'that', instead of 'this'
1247 /* error() - private
1248 * show an error instead of the chart
1250 var error = function(msg) {
1253 if(typeof netdataErrorCallback === 'function') {
1254 ret = netdataErrorCallback('chart', that.id, msg);
1258 that.element.innerHTML = that.id + ': ' + msg;
1259 that.enabled = false;
1260 that.current = that.pan;
1264 // GUID - a unique identifier for the chart
1265 this.uuid = NETDATA.guid();
1267 // string - the name of chart
1268 this.id = self.data('netdata');
1270 // string - the key for localStorage settings
1271 this.settings_id = self.data('id') || null;
1273 // the user given dimensions of the element
1274 this.width = self.data('width') || NETDATA.chartDefaults.width;
1275 this.height = self.data('height') || NETDATA.chartDefaults.height;
1276 this.height_original = this.height;
1278 if(this.settings_id !== null) {
1279 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1280 // this is the callback that will be called
1281 // if and when the user resets all localStorage variables
1282 // to their defaults
1284 resizeChartToHeight(height);
1288 // string - the netdata server URL, without any path
1289 this.host = self.data('host') || NETDATA.chartDefaults.host;
1291 // make sure the host does not end with /
1292 // all netdata API requests use absolute paths
1293 while(this.host.slice(-1) === '/')
1294 this.host = this.host.substring(0, this.host.length - 1);
1296 // string - the grouping method requested by the user
1297 this.method = self.data('method') || NETDATA.chartDefaults.method;
1299 // the time-range requested by the user
1300 this.after = self.data('after') || NETDATA.chartDefaults.after;
1301 this.before = self.data('before') || NETDATA.chartDefaults.before;
1303 // the pixels per point requested by the user
1304 this.pixels_per_point = self.data('pixels-per-point') || 1;
1305 this.points = self.data('points') || null;
1307 // the dimensions requested by the user
1308 this.dimensions = self.data('dimensions') || null;
1310 // the chart library requested by the user
1311 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1313 // how many retries we have made to load chart data from the server
1314 this.retries_on_data_failures = 0;
1316 // object - the chart library used
1317 this.library = null;
1321 this.colors_assigned = {};
1322 this.colors_available = null;
1324 // the element already created by the user
1325 this.element_message = null;
1327 // the element with the chart
1328 this.element_chart = null;
1330 // the element with the legend of the chart (if created by us)
1331 this.element_legend = null;
1332 this.element_legend_childs = {
1337 perfect_scroller: null, // the container to apply perfect scroller to
1341 this.chart_url = null; // string - the url to download chart info
1342 this.chart = null; // object - the chart as downloaded from the server
1344 this.title = self.data('title') || null; // the title of the chart
1345 this.units = self.data('units') || null; // the units of the chart dimensions
1346 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1347 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1349 this.running = false; // boolean - true when the chart is being refreshed now
1350 this.enabled = true; // boolean - is the chart enabled for refresh?
1351 this.paused = false; // boolean - is the chart paused for any reason?
1352 this.selected = false; // boolean - is the chart shown a selection?
1353 this.debug = false; // boolean - console.log() debug info about this chart
1355 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1356 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1357 this.requested_after = null; // milliseconds - the timestamp of the request after param
1358 this.requested_before = null; // milliseconds - the timestamp of the request before param
1359 this.requested_padding = null;
1360 this.view_after = 0;
1361 this.view_before = 0;
1363 this.value_decimal_detail = -1;
1364 var d = self.data('decimal-digits');
1365 if(typeof d === 'number') {
1366 this.value_decimal_detail = 1;
1368 this.value_decimal_detail *= 10;
1374 force_update_at: 0, // the timestamp to force the update at
1375 force_before_ms: null,
1376 force_after_ms: null
1381 force_update_at: 0, // the timestamp to force the update at
1382 force_before_ms: null,
1383 force_after_ms: null
1388 force_update_at: 0, // the timestamp to force the update at
1389 force_before_ms: null,
1390 force_after_ms: null
1393 // this is a pointer to one of the sub-classes below
1395 this.current = this.auto;
1397 // check the requested library is available
1398 // we don't initialize it here - it will be initialized when
1399 // this chart will be first used
1400 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1401 NETDATA.error(402, that.library_name);
1402 error('chart library "' + that.library_name + '" is not found');
1405 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1406 NETDATA.error(403, that.library_name);
1407 error('chart library "' + that.library_name + '" is not enabled');
1411 that.library = NETDATA.chartLibraries[that.library_name];
1413 // milliseconds - the time the last refresh took
1414 this.refresh_dt_ms = 0;
1416 // if we need to report the rendering speed
1417 // find the element that needs to be updated
1418 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1420 if(refresh_dt_element_name !== null) {
1421 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1424 this.refresh_dt_element = null;
1426 this.dimensions_visibility = new dimensionsVisibility(this);
1428 this._updating = false;
1430 // ============================================================================================================
1431 // PRIVATE FUNCTIONS
1433 var createDOM = function() {
1434 if(that.enabled === false) return;
1436 if(that.element_message !== null) that.element_message.innerHTML = '';
1437 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1438 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1440 that.element.innerHTML = '';
1442 that.element_message = document.createElement('div');
1443 that.element_message.className = 'netdata-message icon hidden';
1444 that.element.appendChild(that.element_message);
1446 that.element_chart = document.createElement('div');
1447 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1448 that.element.appendChild(that.element_chart);
1450 if(that.hasLegend() === true) {
1451 that.element.className = "netdata-container-with-legend";
1452 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1454 that.element_legend = document.createElement('div');
1455 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1456 that.element.appendChild(that.element_legend);
1459 that.element.className = "netdata-container";
1460 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1462 that.element_legend = null;
1464 that.element_legend_childs.series = null;
1466 if(typeof(that.width) === 'string')
1467 $(that.element).css('width', that.width);
1468 else if(typeof(that.width) === 'number')
1469 $(that.element).css('width', that.width + 'px');
1471 if(typeof(that.library.aspect_ratio) === 'undefined') {
1472 if(typeof(that.height) === 'string')
1473 that.element.style.height = that.height;
1474 else if(typeof(that.height) === 'number')
1475 that.element.style.height = that.height.toString() + 'px';
1478 var w = that.element.offsetWidth;
1479 if(w === null || w === 0) {
1480 // the div is hidden
1481 // this will resize the chart when next viewed
1482 that.tm.last_resized = 0;
1485 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1488 if(NETDATA.chartDefaults.min_width !== null)
1489 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1491 that.tm.last_dom_created = Date.now();
1497 * initialize state variables
1498 * destroy all (possibly) created state elements
1499 * create the basic DOM for a chart
1501 var init = function() {
1502 if(that.enabled === false) return;
1504 that.paused = false;
1505 that.selected = false;
1507 that.chart_created = false; // boolean - is the library.create() been called?
1508 that.updates_counter = 0; // numeric - the number of refreshes made so far
1509 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1510 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1513 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1514 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1515 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1517 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1518 last_updated: 0, // the timestamp the chart last updated with data
1519 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1521 // Used with NETDATA.globalPanAndZoom.seq
1522 last_visible_check: 0, // the time we last checked if it is visible
1523 last_resized: 0, // the time the chart was resized
1524 last_hidden: 0, // the time the chart was hidden
1525 last_unhidden: 0, // the time the chart was unhidden
1526 last_autorefreshed: 0 // the time the chart was last refreshed
1529 that.data = null; // the last data as downloaded from the netdata server
1530 that.data_url = 'invalid://'; // string - the last url used to update the chart
1531 that.data_points = 0; // number - the number of points returned from netdata
1532 that.data_after = 0; // milliseconds - the first timestamp of the data
1533 that.data_before = 0; // milliseconds - the last timestamp of the data
1534 that.data_update_every = 0; // milliseconds - the frequency to update the data
1536 that.tm.last_initialized = Date.now();
1539 that.setMode('auto');
1542 var maxMessageFontSize = function() {
1543 var screenHeight = screen.height;
1544 var el = that.element;
1546 // normally we want a font size, as tall as the element
1547 var h = el.clientHeight;
1549 // but give it some air, 20% let's say, or 5 pixels min
1550 var lost = Math.max(h * 0.2, 5);
1553 // center the text, vertically
1554 var paddingTop = (lost - 5) / 2;
1556 // but check the width too
1557 // it should fit 10 characters in it
1558 var w = el.clientWidth / 10;
1560 paddingTop += (h - w) / 2;
1564 // and don't make it too huge
1565 // 5% of the screen size is good
1566 if(h > screenHeight / 20) {
1567 paddingTop += (h - (screenHeight / 20)) / 2;
1568 h = screenHeight / 20;
1572 that.element_message.style.fontSize = h.toString() + 'px';
1573 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1576 var showMessageIcon = function(icon) {
1577 that.element_message.innerHTML = icon;
1578 maxMessageFontSize();
1579 $(that.element_message).removeClass('hidden');
1580 that.___messageHidden___ = undefined;
1583 var hideMessage = function() {
1584 if(typeof that.___messageHidden___ === 'undefined') {
1585 that.___messageHidden___ = true;
1586 $(that.element_message).addClass('hidden');
1590 var showRendering = function() {
1592 if(that.chart !== null) {
1593 if(that.chart.chart_type === 'line')
1594 icon = '<i class="fa fa-line-chart"></i>';
1596 icon = '<i class="fa fa-area-chart"></i>';
1599 icon = '<i class="fa fa-area-chart"></i>';
1601 showMessageIcon(icon + ' netdata');
1604 var showLoading = function() {
1605 if(that.chart_created === false) {
1606 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1612 var isHidden = function() {
1613 return (typeof that.___chartIsHidden___ !== 'undefined');
1616 // hide the chart, when it is not visible - called from isVisible()
1617 var hideChart = function() {
1618 // hide it, if it is not already hidden
1619 if(isHidden() === true) return;
1621 if(that.chart_created === true) {
1622 if(NETDATA.options.current.destroy_on_hide === true) {
1623 // we should destroy it
1628 that.element_chart.style.display = 'none';
1629 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1630 that.tm.last_hidden = Date.now();
1633 // This works, but I not sure there are no corner cases somewhere
1634 // so it is commented - if the user has memory issues he can
1635 // set Destroy on Hide for all charts
1636 // that.data = null;
1640 that.___chartIsHidden___ = true;
1643 // unhide the chart, when it is visible - called from isVisible()
1644 var unhideChart = function() {
1645 if(isHidden() === false) return;
1647 that.___chartIsHidden___ = undefined;
1648 that.updates_since_last_unhide = 0;
1650 if(that.chart_created === false) {
1651 // we need to re-initialize it, to show our background
1652 // logo in bootstrap tabs, until the chart loads
1656 that.tm.last_unhidden = Date.now();
1657 that.element_chart.style.display = '';
1658 if(that.element_legend !== null) that.element_legend.style.display = '';
1664 var canBeRendered = function() {
1665 return (isHidden() === false && that.isVisible(true) === true);
1668 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1669 var callChartLibraryUpdateSafely = function(data) {
1672 if(canBeRendered() === false)
1675 if(NETDATA.options.debug.chart_errors === true)
1676 status = that.library.update(that, data);
1679 status = that.library.update(that, data);
1686 if(status === false) {
1687 error('chart failed to be updated as ' + that.library_name);
1694 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1695 var callChartLibraryCreateSafely = function(data) {
1698 if(canBeRendered() === false)
1701 if(NETDATA.options.debug.chart_errors === true)
1702 status = that.library.create(that, data);
1705 status = that.library.create(that, data);
1712 if(status === false) {
1713 error('chart failed to be created as ' + that.library_name);
1717 that.chart_created = true;
1718 that.updates_since_last_creation = 0;
1722 // ----------------------------------------------------------------------------------------------------------------
1725 // resizeChart() - private
1726 // to be called just before the chart library to make sure that
1727 // a properly sized dom is available
1728 var resizeChart = function() {
1729 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1730 if(that.chart_created === false) return;
1732 if(that.needsRecreation()) {
1735 else if(typeof that.library.resize === 'function') {
1736 that.library.resize(that);
1738 if(that.element_legend_childs.perfect_scroller !== null)
1739 Ps.update(that.element_legend_childs.perfect_scroller);
1741 maxMessageFontSize();
1744 that.tm.last_resized = Date.now();
1748 // this is the actual chart resize algorithm
1750 // - resize the entire container
1751 // - update the internal states
1752 // - resize the chart as the div changes height
1753 // - update the scrollbar of the legend
1754 var resizeChartToHeight = function(h) {
1756 that.element.style.height = h;
1758 if(that.settings_id !== null)
1759 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1761 var now = Date.now();
1762 NETDATA.options.last_page_scroll = now;
1763 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1766 that.tm.last_resized = 0;
1770 this.resizeHandler = function(e) {
1773 if(typeof this.event_resize === 'undefined'
1774 || this.event_resize.chart_original_w === 'undefined'
1775 || this.event_resize.chart_original_h === 'undefined')
1776 this.event_resize = {
1777 chart_original_w: this.element.clientWidth,
1778 chart_original_h: this.element.clientHeight,
1782 if(e.type === 'touchstart') {
1783 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1784 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1787 this.event_resize.mouse_start_x = e.clientX;
1788 this.event_resize.mouse_start_y = e.clientY;
1791 this.event_resize.chart_start_w = this.element.clientWidth;
1792 this.event_resize.chart_start_h = this.element.clientHeight;
1793 this.event_resize.chart_last_w = this.element.clientWidth;
1794 this.event_resize.chart_last_h = this.element.clientHeight;
1796 var now = Date.now();
1797 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1798 // double click / double tap event
1800 // console.dir(this.element_legend_childs.content);
1801 // console.dir(this.element_legend_childs.perfect_scroller);
1803 // the optimal height of the chart
1804 // showing the entire legend
1805 var optimal = this.event_resize.chart_last_h
1806 + this.element_legend_childs.perfect_scroller.scrollHeight
1807 - this.element_legend_childs.perfect_scroller.clientHeight;
1809 // if we are not optimal, be optimal
1810 if(this.event_resize.chart_last_h !== optimal) {
1811 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1812 resizeChartToHeight(optimal.toString() + 'px');
1815 // else if the current height is not the original/saved height
1816 // reset to the original/saved height
1817 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1818 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1819 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1822 // else if the current height is not the internal default height
1823 // reset to the internal default height
1824 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1825 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1826 resizeChartToHeight(this.height_original.toString());
1829 // else if the current height is not the firstchild's clientheight
1831 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1832 var parent_rect = this.element.getBoundingClientRect();
1833 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1834 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1836 // console.log(parent_rect);
1837 // console.log(content_rect);
1838 // console.log(wanted);
1840 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1841 if(this.event_resize.chart_last_h !== wanted)
1842 resizeChartToHeight(wanted.toString() + 'px');
1846 this.event_resize.last = now;
1848 // process movement event
1849 document.onmousemove =
1850 document.ontouchmove =
1851 this.element_legend_childs.resize_handler.onmousemove =
1852 this.element_legend_childs.resize_handler.ontouchmove =
1857 case 'mousemove': y = e.clientY; break;
1858 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1862 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1864 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1865 resizeChartToHeight(newH.toString() + 'px');
1866 that.event_resize.chart_last_h = newH;
1871 // process end event
1872 document.onmouseup =
1873 document.ontouchend =
1874 this.element_legend_childs.resize_handler.onmouseup =
1875 this.element_legend_childs.resize_handler.ontouchend =
1879 // remove all the hooks
1880 document.onmouseup =
1881 document.onmousemove =
1882 document.ontouchmove =
1883 document.ontouchend =
1884 that.element_legend_childs.resize_handler.onmousemove =
1885 that.element_legend_childs.resize_handler.ontouchmove =
1886 that.element_legend_childs.resize_handler.onmouseout =
1887 that.element_legend_childs.resize_handler.onmouseup =
1888 that.element_legend_childs.resize_handler.ontouchend =
1891 // allow auto-refreshes
1892 NETDATA.options.auto_refresher_stop_until = 0;
1898 var noDataToShow = function() {
1899 showMessageIcon('<i class="fa fa-warning"></i> empty');
1900 that.legendUpdateDOM();
1901 that.tm.last_autorefreshed = Date.now();
1902 // that.data_update_every = 30 * 1000;
1903 //that.element_chart.style.display = 'none';
1904 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1905 //that.___chartIsHidden___ = true;
1908 // ============================================================================================================
1911 this.error = function(msg) {
1915 this.setMode = function(m) {
1916 if(this.current !== null && this.current.name === m) return;
1919 this.current = this.auto;
1920 else if(m === 'pan')
1921 this.current = this.pan;
1922 else if(m === 'zoom')
1923 this.current = this.zoom;
1925 this.current = this.auto;
1927 this.current.force_update_at = 0;
1928 this.current.force_before_ms = null;
1929 this.current.force_after_ms = null;
1931 this.tm.last_mode_switch = Date.now();
1934 // ----------------------------------------------------------------------------------------------------------------
1935 // global selection sync
1937 // prevent to global selection sync for some time
1938 this.globalSelectionSyncDelay = function(ms) {
1939 if(NETDATA.options.current.sync_selection === false)
1942 if(typeof ms === 'number')
1943 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1945 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1948 // can we globally apply selection sync?
1949 this.globalSelectionSyncAbility = function() {
1950 if(NETDATA.options.current.sync_selection === false)
1953 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1956 this.globalSelectionSyncIsMaster = function() {
1957 return (NETDATA.globalSelectionSync.state === this);
1960 // this chart is the master of the global selection sync
1961 this.globalSelectionSyncBeMaster = function() {
1963 if(this.globalSelectionSyncIsMaster()) {
1964 if(this.debug === true)
1965 this.log('sync: I am the master already.');
1970 if(NETDATA.globalSelectionSync.state) {
1971 if(this.debug === true)
1972 this.log('sync: I am not the sync master. Resetting global sync.');
1974 this.globalSelectionSyncStop();
1977 // become the master
1978 if(this.debug === true)
1979 this.log('sync: becoming sync master.');
1981 this.selected = true;
1982 NETDATA.globalSelectionSync.state = this;
1984 // find the all slaves
1985 var targets = NETDATA.options.targets;
1986 var len = targets.length;
1988 var st = targets[len];
1991 if(this.debug === true)
1992 st.log('sync: not adding me to sync');
1994 else if(st.globalSelectionSyncIsEligible()) {
1995 if(this.debug === true)
1996 st.log('sync: adding to sync as slave');
1998 st.globalSelectionSyncBeSlave();
2002 // this.globalSelectionSyncDelay(100);
2005 // can the chart participate to the global selection sync as a slave?
2006 this.globalSelectionSyncIsEligible = function() {
2007 return (this.enabled === true
2008 && this.library !== null
2009 && typeof this.library.setSelection === 'function'
2010 && this.isVisible() === true
2011 && this.chart_created === true);
2014 // this chart becomes a slave of the global selection sync
2015 this.globalSelectionSyncBeSlave = function() {
2016 if(NETDATA.globalSelectionSync.state !== this)
2017 NETDATA.globalSelectionSync.slaves.push(this);
2020 // sync all the visible charts to the given time
2021 // this is to be called from the chart libraries
2022 this.globalSelectionSync = function(t) {
2023 if(this.globalSelectionSyncAbility() === false)
2026 if(this.globalSelectionSyncIsMaster() === false) {
2027 if(this.debug === true)
2028 this.log('sync: trying to be sync master.');
2030 this.globalSelectionSyncBeMaster();
2032 if(this.globalSelectionSyncAbility() === false)
2036 NETDATA.globalSelectionSync.last_t = t;
2037 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2042 // stop syncing all charts to the given time
2043 this.globalSelectionSyncStop = function() {
2044 if(NETDATA.globalSelectionSync.slaves.length) {
2045 if(this.debug === true)
2046 this.log('sync: cleaning up...');
2048 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2050 if(that.debug === true)
2051 st.log('sync: not adding me to sync stop');
2054 if(that.debug === true)
2055 st.log('sync: removed slave from sync');
2057 st.clearSelection();
2061 NETDATA.globalSelectionSync.last_t = 0;
2062 NETDATA.globalSelectionSync.slaves = [];
2063 NETDATA.globalSelectionSync.state = null;
2066 this.clearSelection();
2069 this.setSelection = function(t) {
2070 if(typeof this.library.setSelection === 'function')
2071 this.selected = (this.library.setSelection(this, t) === true);
2073 this.selected = true;
2075 if(this.selected === true && this.debug === true)
2076 this.log('selection set to ' + t.toString());
2078 return this.selected;
2081 this.clearSelection = function() {
2082 if(this.selected === true) {
2083 if(typeof this.library.clearSelection === 'function')
2084 this.selected = (this.library.clearSelection(this) !== true);
2086 this.selected = false;
2088 if(this.selected === false && this.debug === true)
2089 this.log('selection cleared');
2094 return this.selected;
2097 // find if a timestamp (ms) is shown in the current chart
2098 this.timeIsVisible = function(t) {
2099 return (t >= this.data_after && t <= this.data_before);
2102 this.calculateRowForTime = function(t) {
2103 if(this.timeIsVisible(t) === false) return -1;
2104 return Math.floor((t - this.data_after) / this.data_update_every);
2107 // ----------------------------------------------------------------------------------------------------------------
2110 this.log = function(msg) {
2111 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2114 this.pauseChart = function() {
2115 if(this.paused === false) {
2116 if(this.debug === true)
2117 this.log('pauseChart()');
2123 this.unpauseChart = function() {
2124 if(this.paused === true) {
2125 if(this.debug === true)
2126 this.log('unpauseChart()');
2128 this.paused = false;
2132 this.resetChart = function(dont_clear_master, dont_update) {
2133 if(this.debug === true)
2134 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2136 if(typeof dont_clear_master === 'undefined')
2137 dont_clear_master = false;
2139 if(typeof dont_update === 'undefined')
2140 dont_update = false;
2142 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2143 if(this.debug === true)
2144 this.log('resetChart() diverting to clearMaster().');
2145 // this will call us back with master === true
2146 NETDATA.globalPanAndZoom.clearMaster();
2150 this.clearSelection();
2152 this.tm.pan_and_zoom_seq = 0;
2154 this.setMode('auto');
2155 this.current.force_update_at = 0;
2156 this.current.force_before_ms = null;
2157 this.current.force_after_ms = null;
2158 this.tm.last_autorefreshed = 0;
2159 this.paused = false;
2160 this.selected = false;
2161 this.enabled = true;
2162 // this.debug = false;
2164 // do not update the chart here
2165 // or the chart will flip-flop when it is the master
2166 // of a selection sync and another chart becomes
2169 if(dont_update !== true && this.isVisible() === true) {
2174 this.updateChartPanOrZoom = function(after, before) {
2175 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2178 if(this.debug === true)
2181 if(before < after) {
2182 if(this.debug === true)
2183 this.log(logme + 'flipped parameters, rejecting it.');
2188 if(typeof this.fixed_min_duration === 'undefined')
2189 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2191 var min_duration = this.fixed_min_duration;
2192 var current_duration = Math.round(this.view_before - this.view_after);
2194 // round the numbers
2195 after = Math.round(after);
2196 before = Math.round(before);
2198 // align them to update_every
2199 // stretching them further away
2200 after -= after % this.data_update_every;
2201 before += this.data_update_every - (before % this.data_update_every);
2203 // the final wanted duration
2204 var wanted_duration = before - after;
2206 // to allow panning, accept just a point below our minimum
2207 if((current_duration - this.data_update_every) < min_duration)
2208 min_duration = current_duration - this.data_update_every;
2210 // we do it, but we adjust to minimum size and return false
2211 // when the wanted size is below the current and the minimum
2213 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2214 if(this.debug === true)
2215 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2217 min_duration = this.fixed_min_duration;
2219 var dt = (min_duration - wanted_duration) / 2;
2222 wanted_duration = before - after;
2226 var tolerance = this.data_update_every * 2;
2227 var movement = Math.abs(before - this.view_before);
2229 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2230 if(this.debug === true)
2231 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2235 if(this.current.name === 'auto') {
2236 this.log(logme + 'caller called me with mode: ' + this.current.name);
2237 this.setMode('pan');
2240 if(this.debug === true)
2241 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2243 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2244 this.current.force_after_ms = after;
2245 this.current.force_before_ms = before;
2246 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2250 var __legendFormatValueChartDecimalsLastMin = undefined;
2251 var __legendFormatValueChartDecimalsLastMax = undefined;
2252 var __legendFormatValueChartDecimals = -1;
2253 this.legendFormatValueDecimalsFromMinMax = function(min, max) {
2254 if(min === __legendFormatValueChartDecimalsLastMin && max === __legendFormatValueChartDecimalsLastMax)
2257 __legendFormatValueChartDecimalsLastMin = min;
2258 __legendFormatValueChartDecimalsLastMax = max;
2263 delta = Math.abs(min);
2265 delta = Math.abs(max - min);
2267 if(delta > 1000) __legendFormatValueChartDecimals = 0;
2268 else if(delta > 10 ) __legendFormatValueChartDecimals = 1;
2269 else if(delta > 1 ) __legendFormatValueChartDecimals = 2;
2270 else if(delta > 0.1 ) __legendFormatValueChartDecimals = 3;
2271 else __legendFormatValueChartDecimals = 4;
2274 this.legendFormatValue = function(value) {
2275 if(typeof value !== 'number') return '-';
2279 if(this.value_decimal_detail !== -1) {
2280 dmin = dmax = this.value_decimal_detail;
2283 if(__legendFormatValueChartDecimals < 0) {
2286 if(abs > 1000) dmax = 0;
2287 else if(abs > 10 ) dmax = 1;
2288 else if(abs > 1) dmax = 2;
2289 else if(abs > 0.1) dmax = 3;
2293 dmin = dmax = __legendFormatValueChartDecimals;
2296 return value.toLocaleString(undefined, {
2297 // style: 'decimal',
2298 // minimumIntegerDigits: 1,
2299 // minimumSignificantDigits: 1,
2300 // maximumSignificantDigits: 1,
2302 minimumFractionDigits: dmin,
2303 maximumFractionDigits: dmax
2307 this.legendSetLabelValue = function(label, value) {
2308 var series = this.element_legend_childs.series[label];
2309 if(typeof series === 'undefined') return;
2310 if(series.value === null && series.user === null) return;
2313 // this slows down firefox and edge significantly
2314 // since it requires to use innerHTML(), instead of innerText()
2316 // if the value has not changed, skip DOM update
2317 //if(series.last === value) return;
2320 if(typeof value === 'number') {
2321 var v = Math.abs(value);
2322 s = r = this.legendFormatValue(value);
2324 if(typeof series.last === 'number') {
2325 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2326 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2327 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2329 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2339 series.last = value;
2343 var s = this.legendFormatValue(value);
2345 // caching: do not update the update to show the same value again
2346 if(s === series.last_shown_value) return;
2347 series.last_shown_value = s;
2349 if(series.value !== null) series.value.innerText = s;
2350 if(series.user !== null) series.user.innerText = s;
2353 this.__legendSetDateString = function(date) {
2354 if(date !== this.__last_shown_legend_date) {
2355 this.element_legend_childs.title_date.innerText = date;
2356 this.__last_shown_legend_date = date;
2360 this.__legendSetTimeString = function(time) {
2361 if(time !== this.__last_shown_legend_time) {
2362 this.element_legend_childs.title_time.innerText = time;
2363 this.__last_shown_legend_time = time;
2367 this.__legendSetUnitsString = function(units) {
2368 if(units !== this.__last_shown_legend_units) {
2369 this.element_legend_childs.title_units.innerText = units;
2370 this.__last_shown_legend_units = units;
2374 this.legendSetDateLast = {
2380 this.legendSetDate = function(ms) {
2381 if(typeof ms !== 'number') {
2382 this.legendShowUndefined();
2386 if(this.legendSetDateLast.ms !== ms) {
2387 var d = new Date(ms);
2388 this.legendSetDateLast.ms = ms;
2389 this.legendSetDateLast.date = d.toLocaleDateString();
2390 this.legendSetDateLast.time = d.toLocaleTimeString();
2393 if(this.element_legend_childs.title_date !== null)
2394 this.__legendSetDateString(this.legendSetDateLast.date);
2396 if(this.element_legend_childs.title_time !== null)
2397 this.__legendSetTimeString(this.legendSetDateLast.time);
2399 if(this.element_legend_childs.title_units !== null)
2400 this.__legendSetUnitsString(this.units)
2403 this.legendShowUndefined = function() {
2404 if(this.element_legend_childs.title_date !== null)
2405 this.__legendSetDateString(' ');
2407 if(this.element_legend_childs.title_time !== null)
2408 this.__legendSetTimeString(this.chart.name);
2410 if(this.element_legend_childs.title_units !== null)
2411 this.__legendSetUnitsString(' ');
2413 if(this.data && this.element_legend_childs.series !== null) {
2414 var labels = this.data.dimension_names;
2415 var i = labels.length;
2417 var label = labels[i];
2419 if(typeof label === 'undefined' || typeof this.element_legend_childs.series[label] === 'undefined') continue;
2420 this.legendSetLabelValue(label, null);
2425 this.legendShowLatestValues = function() {
2426 if(this.chart === null) return;
2427 if(this.selected) return;
2429 if(this.data === null || this.element_legend_childs.series === null) {
2430 this.legendShowUndefined();
2434 var show_undefined = true;
2435 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2436 show_undefined = false;
2438 if(show_undefined) {
2439 this.legendShowUndefined();
2443 this.legendSetDate(this.view_before);
2445 var labels = this.data.dimension_names;
2446 var i = labels.length;
2448 var label = labels[i];
2450 if(typeof label === 'undefined') continue;
2451 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2454 this.legendSetLabelValue(label, null);
2456 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2460 this.legendReset = function() {
2461 this.legendShowLatestValues();
2464 // this should be called just ONCE per dimension per chart
2465 this._chartDimensionColor = function(label) {
2466 if(this.colors === null) this.chartColors();
2468 if(typeof this.colors_assigned[label] === 'undefined') {
2469 if(this.colors_available.length === 0) {
2470 var len = NETDATA.themes.current.colors.length;
2472 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2475 this.colors_assigned[label] = this.colors_available.shift();
2477 if(this.debug === true)
2478 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2481 if(this.debug === true)
2482 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2485 this.colors.push(this.colors_assigned[label]);
2486 return this.colors_assigned[label];
2489 this.chartColors = function() {
2490 if(this.colors !== null) return this.colors;
2493 this.colors_available = [];
2495 // add the standard colors
2496 var len = NETDATA.themes.current.colors.length;
2498 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2500 // add the user supplied colors
2501 var c = $(this.element).data('colors');
2502 // this.log('read colors: ' + c);
2503 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2504 if(typeof c !== 'string') {
2505 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2515 this.colors_available.unshift(c[len]);
2516 // this.log('adding color: ' + c[len]);
2525 this.legendUpdateDOM = function() {
2526 var needed = false, dim, keys, len, i;
2528 // check that the legend DOM is up to date for the downloaded dimensions
2529 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2530 // this.log('the legend does not have any series - requesting legend update');
2533 else if(this.data === null) {
2534 // this.log('the chart does not have any data - requesting legend update');
2537 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2541 var labels = this.data.dimension_names.toString();
2542 if(labels !== this.element_legend_childs.series.labels_key) {
2545 if(this.debug === true)
2546 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2550 if(needed === false) {
2551 // make sure colors available
2554 // do we have to update the current values?
2555 // we do this, only when the visible chart is current
2556 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2557 if(this.debug === true)
2558 this.log('chart is in latest position... updating values on legend...');
2560 //var labels = this.data.dimension_names;
2561 //var i = labels.length;
2563 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2567 if(this.colors === null) {
2568 // this is the first time we update the chart
2569 // let's assign colors to all dimensions
2570 if(this.library.track_colors() === true) {
2571 keys = Object.keys(this.chart.dimensions);
2573 for(i = 0; i < len ;i++)
2574 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2577 // we will re-generate the colors for the chart
2578 // based on the selected dimensions
2581 if(this.debug === true)
2582 this.log('updating Legend DOM');
2584 // mark all dimensions as invalid
2585 this.dimensions_visibility.invalidateAll();
2587 var genLabel = function(state, parent, dim, name, count) {
2588 var color = state._chartDimensionColor(name);
2590 var user_element = null;
2591 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2592 if(user_id === null)
2593 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2594 if(user_id !== null) {
2595 user_element = document.getElementById(user_id) || null;
2596 if (user_element === null)
2597 state.log('Cannot find element with id: ' + user_id);
2600 state.element_legend_childs.series[name] = {
2601 name: document.createElement('span'),
2602 value: document.createElement('span'),
2605 last_shown_value: null
2608 var label = state.element_legend_childs.series[name];
2610 // create the dimension visibility tracking for this label
2611 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2613 var rgb = NETDATA.colorHex2Rgb(color);
2614 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2615 + state.chart.chart_type
2616 + '" style="background-color: '
2617 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2618 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2620 var text = document.createTextNode(' ' + name);
2621 label.name.appendChild(text);
2624 parent.appendChild(document.createElement('br'));
2626 parent.appendChild(label.name);
2627 parent.appendChild(label.value);
2630 var content = document.createElement('div');
2632 if(this.hasLegend()) {
2633 this.element_legend_childs = {
2635 resize_handler: document.createElement('div'),
2636 toolbox: document.createElement('div'),
2637 toolbox_left: document.createElement('div'),
2638 toolbox_right: document.createElement('div'),
2639 toolbox_reset: document.createElement('div'),
2640 toolbox_zoomin: document.createElement('div'),
2641 toolbox_zoomout: document.createElement('div'),
2642 toolbox_volume: document.createElement('div'),
2643 title_date: document.createElement('span'),
2644 title_time: document.createElement('span'),
2645 title_units: document.createElement('span'),
2646 perfect_scroller: document.createElement('div'),
2650 this.element_legend.innerHTML = '';
2652 if(this.library.toolboxPanAndZoom !== null) {
2654 var get_pan_and_zoom_step = function(event) {
2656 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2658 else if (event.shiftKey)
2659 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2661 else if (event.altKey)
2662 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2665 return NETDATA.options.current.pan_and_zoom_factor;
2668 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2669 this.element.appendChild(this.element_legend_childs.toolbox);
2671 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2672 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2673 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2674 this.element_legend_childs.toolbox_left.onclick = function(e) {
2677 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2678 var before = that.view_before - step;
2679 var after = that.view_after - step;
2680 if(after >= that.netdata_first)
2681 that.library.toolboxPanAndZoom(that, after, before);
2683 if(NETDATA.options.current.show_help === true)
2684 $(this.element_legend_childs.toolbox_left).popover({
2689 placement: 'bottom',
2690 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2692 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>'
2696 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2697 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2698 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2699 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2701 NETDATA.resetAllCharts(that);
2703 if(NETDATA.options.current.show_help === true)
2704 $(this.element_legend_childs.toolbox_reset).popover({
2709 placement: 'bottom',
2710 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2711 title: 'Chart Reset',
2712 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>'
2715 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2716 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2717 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2718 this.element_legend_childs.toolbox_right.onclick = function(e) {
2720 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2721 var before = that.view_before + step;
2722 var after = that.view_after + step;
2723 if(before <= that.netdata_last)
2724 that.library.toolboxPanAndZoom(that, after, before);
2726 if(NETDATA.options.current.show_help === true)
2727 $(this.element_legend_childs.toolbox_right).popover({
2732 placement: 'bottom',
2733 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2735 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>'
2739 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2740 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2741 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2742 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2744 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2745 var before = that.view_before - dt;
2746 var after = that.view_after + dt;
2747 that.library.toolboxPanAndZoom(that, after, before);
2749 if(NETDATA.options.current.show_help === true)
2750 $(this.element_legend_childs.toolbox_zoomin).popover({
2755 placement: 'bottom',
2756 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2757 title: 'Chart Zoom In',
2758 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>'
2761 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2762 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2763 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2764 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2766 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);
2767 var before = that.view_before + dt;
2768 var after = that.view_after - dt;
2770 that.library.toolboxPanAndZoom(that, after, before);
2772 if(NETDATA.options.current.show_help === true)
2773 $(this.element_legend_childs.toolbox_zoomout).popover({
2778 placement: 'bottom',
2779 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2780 title: 'Chart Zoom Out',
2781 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>'
2784 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2785 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2786 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2787 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2788 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2789 //e.preventDefault();
2790 //alert('clicked toolbox_volume on ' + that.id);
2794 this.element_legend_childs.toolbox = null;
2795 this.element_legend_childs.toolbox_left = null;
2796 this.element_legend_childs.toolbox_reset = null;
2797 this.element_legend_childs.toolbox_right = null;
2798 this.element_legend_childs.toolbox_zoomin = null;
2799 this.element_legend_childs.toolbox_zoomout = null;
2800 this.element_legend_childs.toolbox_volume = null;
2803 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2804 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2805 this.element.appendChild(this.element_legend_childs.resize_handler);
2806 if(NETDATA.options.current.show_help === true)
2807 $(this.element_legend_childs.resize_handler).popover({
2812 placement: 'bottom',
2813 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2814 title: 'Chart Resize',
2815 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>'
2819 this.element_legend_childs.resize_handler.onmousedown =
2821 that.resizeHandler(e);
2825 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2826 that.resizeHandler(e);
2829 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2830 this.element_legend.appendChild(this.element_legend_childs.title_date);
2831 this.__last_shown_legend_date = undefined;
2833 this.element_legend.appendChild(document.createElement('br'));
2835 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2836 this.element_legend.appendChild(this.element_legend_childs.title_time);
2837 this.__last_shown_legend_time = undefined;
2839 this.element_legend.appendChild(document.createElement('br'));
2841 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2842 this.element_legend.appendChild(this.element_legend_childs.title_units);
2843 this.__last_shown_legend_units = undefined;
2845 this.element_legend.appendChild(document.createElement('br'));
2847 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2848 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2850 content.className = 'netdata-legend-series-content';
2851 this.element_legend_childs.perfect_scroller.appendChild(content);
2853 if(NETDATA.options.current.show_help === true)
2854 $(content).popover({
2859 placement: 'bottom',
2860 title: 'Chart Legend',
2861 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2862 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>'
2866 this.element_legend_childs = {
2868 resize_handler: null,
2871 toolbox_right: null,
2872 toolbox_reset: null,
2873 toolbox_zoomin: null,
2874 toolbox_zoomout: null,
2875 toolbox_volume: null,
2879 perfect_scroller: null,
2885 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2886 if(this.debug === true)
2887 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2889 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2890 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2895 keys = Object.keys(this.chart.dimensions);
2896 for(i = 0, len = keys.length; i < len ;i++) {
2898 tmp.push(this.chart.dimensions[dim].name);
2899 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2901 this.element_legend_childs.series.labels_key = tmp.toString();
2902 if(this.debug === true)
2903 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2906 // create a hidden div to be used for hidding
2907 // the original legend of the chart library
2908 var el = document.createElement('div');
2909 if(this.element_legend !== null)
2910 this.element_legend.appendChild(el);
2911 el.style.display = 'none';
2913 this.element_legend_childs.hidden = document.createElement('div');
2914 el.appendChild(this.element_legend_childs.hidden);
2916 if(this.element_legend_childs.perfect_scroller !== null) {
2917 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2919 wheelPropagation: true,
2920 swipePropagation: true,
2921 minScrollbarLength: null,
2922 maxScrollbarLength: null,
2923 useBothWheelAxes: false,
2924 suppressScrollX: true,
2925 suppressScrollY: false,
2926 scrollXMarginOffset: 0,
2927 scrollYMarginOffset: 0,
2930 Ps.update(this.element_legend_childs.perfect_scroller);
2933 this.legendShowLatestValues();
2936 this.hasLegend = function() {
2937 if(typeof this.___hasLegendCache___ !== 'undefined')
2938 return this.___hasLegendCache___;
2941 if(this.library && this.library.legend(this) === 'right-side') {
2942 var legend = $(this.element).data('legend') || 'yes';
2943 if(legend === 'yes') leg = true;
2946 this.___hasLegendCache___ = leg;
2950 this.legendWidth = function() {
2951 return (this.hasLegend())?140:0;
2954 this.legendHeight = function() {
2955 return $(this.element).height();
2958 this.chartWidth = function() {
2959 return $(this.element).width() - this.legendWidth();
2962 this.chartHeight = function() {
2963 return $(this.element).height();
2966 this.chartPixelsPerPoint = function() {
2967 // force an options provided detail
2968 var px = this.pixels_per_point;
2970 if(this.library && px < this.library.pixels_per_point(this))
2971 px = this.library.pixels_per_point(this);
2973 if(px < NETDATA.options.current.pixels_per_point)
2974 px = NETDATA.options.current.pixels_per_point;
2979 this.needsRecreation = function() {
2981 this.chart_created === true
2983 && this.library.autoresize() === false
2984 && this.tm.last_resized < NETDATA.options.last_resized
2988 this.chartURL = function() {
2989 var after, before, points_multiplier = 1;
2990 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2991 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2993 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2994 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2995 this.view_after = after * 1000;
2996 this.view_before = before * 1000;
2998 this.requested_padding = null;
2999 points_multiplier = 1;
3001 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
3002 this.tm.pan_and_zoom_seq = 0;
3004 before = Math.round(this.current.force_before_ms / 1000);
3005 after = Math.round(this.current.force_after_ms / 1000);
3006 this.view_after = after * 1000;
3007 this.view_before = before * 1000;
3009 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
3010 this.requested_padding = Math.round((before - after) / 2);
3011 after -= this.requested_padding;
3012 before += this.requested_padding;
3013 this.requested_padding *= 1000;
3014 points_multiplier = 2;
3017 this.current.force_before_ms = null;
3018 this.current.force_after_ms = null;
3021 this.tm.pan_and_zoom_seq = 0;
3023 before = this.before;
3025 this.view_after = after * 1000;
3026 this.view_before = before * 1000;
3028 this.requested_padding = null;
3029 points_multiplier = 1;
3032 this.requested_after = after * 1000;
3033 this.requested_before = before * 1000;
3035 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3037 // build the data URL
3038 this.data_url = this.host + this.chart.data_url;
3039 this.data_url += "&format=" + this.library.format();
3040 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
3041 this.data_url += "&group=" + this.method;
3043 if(this.override_options !== null)
3044 this.data_url += "&options=" + this.override_options.toString();
3046 this.data_url += "&options=" + this.library.options(this);
3048 this.data_url += '|jsonwrap';
3050 if(NETDATA.options.current.eliminate_zero_dimensions === true)
3051 this.data_url += '|nonzero';
3053 if(this.append_options !== null)
3054 this.data_url += '|' + this.append_options.toString();
3057 this.data_url += "&after=" + after.toString();
3060 this.data_url += "&before=" + before.toString();
3063 this.data_url += "&dimensions=" + this.dimensions;
3065 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3066 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3069 this.redrawChart = function() {
3070 if(this.data !== null)
3071 this.updateChartWithData(this.data);
3074 this.updateChartWithData = function(data) {
3075 if(this.debug === true)
3076 this.log('updateChartWithData() called.');
3078 // this may force the chart to be re-created
3082 this.updates_counter++;
3083 this.updates_since_last_unhide++;
3084 this.updates_since_last_creation++;
3086 var started = Date.now();
3088 // if the result is JSON, find the latest update-every
3089 this.data_update_every = data.view_update_every * 1000;
3090 this.data_after = data.after * 1000;
3091 this.data_before = data.before * 1000;
3092 this.netdata_first = data.first_entry * 1000;
3093 this.netdata_last = data.last_entry * 1000;
3094 this.data_points = data.points;
3097 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3098 if(this.view_after < this.data_after) {
3099 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3100 this.view_after = this.data_after;
3103 if(this.view_before > this.data_before) {
3104 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3105 this.view_before = this.data_before;
3109 this.view_after = this.data_after;
3110 this.view_before = this.data_before;
3113 if(this.debug === true) {
3114 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3116 if(this.current.force_after_ms)
3117 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3119 this.log('STATUS: forced : unset');
3121 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3122 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3123 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3124 this.log('STATUS: points : ' + (this.data_points).toString());
3127 if(this.data_points === 0) {
3132 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3133 if(this.debug === true)
3134 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3136 this.chart_created = false;
3139 // check and update the legend
3140 this.legendUpdateDOM();
3142 if(this.chart_created === true
3143 && typeof this.library.update === 'function') {
3145 if(this.debug === true)
3146 this.log('updating chart...');
3148 if(callChartLibraryUpdateSafely(data) === false)
3152 if(this.debug === true)
3153 this.log('creating chart...');
3155 if(callChartLibraryCreateSafely(data) === false)
3159 this.legendShowLatestValues();
3160 if(this.selected === true)
3161 NETDATA.globalSelectionSync.stop();
3163 // update the performance counters
3164 var now = Date.now();
3165 this.tm.last_updated = now;
3167 // don't update last_autorefreshed if this chart is
3168 // forced to be updated with global PanAndZoom
3169 if(NETDATA.globalPanAndZoom.isActive())
3170 this.tm.last_autorefreshed = 0;
3172 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3173 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3175 this.tm.last_autorefreshed = now;
3178 this.refresh_dt_ms = now - started;
3179 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3181 if(this.refresh_dt_element !== null)
3182 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3185 this.updateChart = function(callback) {
3186 if(this.debug === true)
3187 this.log('updateChart() called.');
3189 if(this._updating === true) {
3190 if(this.debug === true)
3191 this.log('I am already updating...');
3193 if(typeof callback === 'function')
3199 // due to late initialization of charts and libraries
3200 // we need to check this too
3201 if(this.enabled === false) {
3202 if(this.debug === true)
3203 this.log('I am not enabled');
3205 if(typeof callback === 'function')
3211 if(canBeRendered() === false) {
3212 if(typeof callback === 'function')
3218 if(this.chart === null)
3219 return this.getChart(function() {
3220 return that.updateChart(callback);
3223 if(this.library.initialized === false) {
3224 if(this.library.enabled === true) {
3225 return this.library.initialize(function () {
3226 return that.updateChart(callback);
3230 error('chart library "' + this.library_name + '" is not available.');
3232 if(typeof callback === 'function')
3239 this.clearSelection();
3242 if(this.debug === true)
3243 this.log('updating from ' + this.data_url);
3245 NETDATA.statistics.refreshes_total++;
3246 NETDATA.statistics.refreshes_active++;
3248 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3249 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3251 this._updating = true;
3253 this.xhr = $.ajax( {
3258 'Cache-Control': 'no-cache, no-store',
3259 'Pragma': 'no-cache'
3261 xhrFields: { withCredentials: true } // required for the cookie
3263 .done(function(data) {
3264 that.xhr = undefined;
3265 that.retries_on_data_failures = 0;
3267 if(that.debug === true)
3268 that.log('data received. updating chart.');
3270 that.updateChartWithData(data);
3272 .fail(function(msg) {
3273 that.xhr = undefined;
3275 if(msg.statusText !== 'abort') {
3276 that.retries_on_data_failures++;
3277 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3278 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3279 that.retries_on_data_failures = 0;
3280 error('data download failed for url: ' + that.data_url);
3283 that.tm.last_autorefreshed = Date.now();
3284 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3288 .always(function() {
3289 that.xhr = undefined;
3291 NETDATA.statistics.refreshes_active--;
3292 that._updating = false;
3294 if(typeof callback === 'function')
3299 this.isVisible = function(nocache) {
3300 if(typeof nocache === 'undefined')
3303 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3305 // caching - we do not evaluate the charts visibility
3306 // if the page has not been scrolled since the last check
3307 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3308 return this.___isVisible___;
3310 this.tm.last_visible_check = Date.now();
3312 var wh = window.innerHeight;
3313 var x = this.element.getBoundingClientRect();
3317 if(x.width === 0 || x.height === 0) {
3319 this.___isVisible___ = false;
3320 return this.___isVisible___;
3323 if(x.top < 0 && -x.top > x.height) {
3324 // the chart is entirely above
3325 ret = -x.top - x.height;
3327 else if(x.top > wh) {
3328 // the chart is entirely below
3332 if(ret > tolerance) {
3333 // the chart is too far
3336 this.___isVisible___ = false;
3337 return this.___isVisible___;
3340 // the chart is inside or very close
3343 this.___isVisible___ = true;
3344 return this.___isVisible___;
3348 this.isAutoRefreshable = function() {
3349 return (this.current.autorefresh);
3352 this.canBeAutoRefreshed = function() {
3353 var now = Date.now();
3355 if(this.running === true) {
3356 if(this.debug === true)
3357 this.log('I am already running');
3362 if(this.enabled === false) {
3363 if(this.debug === true)
3364 this.log('I am not enabled');
3369 if(this.library === null || this.library.enabled === false) {
3370 error('charting library "' + this.library_name + '" is not available');
3371 if(this.debug === true)
3372 this.log('My chart library ' + this.library_name + ' is not available');
3377 if(this.isVisible() === false) {
3378 if(NETDATA.options.debug.visibility === true || this.debug === true)
3379 this.log('I am not visible');
3384 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3385 if(this.debug === true)
3386 this.log('timed force update detected - allowing this update');
3388 this.current.force_update_at = 0;
3392 if(this.isAutoRefreshable() === true) {
3393 // allow the first update, even if the page is not visible
3394 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3395 if(NETDATA.options.debug.focus === true || this.debug === true)
3396 this.log('canBeAutoRefreshed(): page does not have focus');
3401 if(this.needsRecreation() === true) {
3402 if(this.debug === true)
3403 this.log('canBeAutoRefreshed(): needs re-creation.');
3408 // options valid only for autoRefresh()
3409 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3410 if(NETDATA.globalPanAndZoom.isActive()) {
3411 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3412 if(this.debug === true)
3413 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3418 if(this.debug === true)
3419 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3425 if(this.selected === true) {
3426 if(this.debug === true)
3427 this.log('canBeAutoRefreshed(): I have a selection in place.');
3432 if(this.paused === true) {
3433 if(this.debug === true)
3434 this.log('canBeAutoRefreshed(): I am paused.');
3439 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3440 if(this.debug === true)
3441 this.log('canBeAutoRefreshed(): It is time to update me.');
3451 this.autoRefresh = function(callback) {
3452 if(this.canBeAutoRefreshed() === true && this.running === false) {
3455 state.running = true;
3456 state.updateChart(function() {
3457 state.running = false;
3459 if(typeof callback !== 'undefined')
3464 if(typeof callback !== 'undefined')
3469 this._defaultsFromDownloadedChart = function(chart) {
3471 this.chart_url = chart.url;
3472 this.data_update_every = chart.update_every * 1000;
3473 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3474 this.tm.last_info_downloaded = Date.now();
3476 if(this.title === null)
3477 this.title = chart.title;
3479 if(this.units === null)
3480 this.units = chart.units;
3483 // fetch the chart description from the netdata server
3484 this.getChart = function(callback) {
3485 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3487 this._defaultsFromDownloadedChart(this.chart);
3489 if(typeof callback === 'function')
3493 this.chart_url = "/api/v1/chart?chart=" + this.id;
3495 if(this.debug === true)
3496 this.log('downloading ' + this.chart_url);
3499 url: this.host + this.chart_url,
3502 xhrFields: { withCredentials: true } // required for the cookie
3504 .done(function(chart) {
3505 chart.url = that.chart_url;
3506 that._defaultsFromDownloadedChart(chart);
3507 NETDATA.chartRegistry.add(that.host, that.id, chart);
3510 NETDATA.error(404, that.chart_url);
3511 error('chart not found on url "' + that.chart_url + '"');
3513 .always(function() {
3514 if(typeof callback === 'function')
3520 // ============================================================================================================
3526 NETDATA.resetAllCharts = function(state) {
3527 // first clear the global selection sync
3528 // to make sure no chart is in selected state
3529 state.globalSelectionSyncStop();
3531 // there are 2 possibilities here
3532 // a. state is the global Pan and Zoom master
3533 // b. state is not the global Pan and Zoom master
3535 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3538 // clear the global Pan and Zoom
3539 // this will also refresh the master
3540 // and unblock any charts currently mirroring the master
3541 NETDATA.globalPanAndZoom.clearMaster();
3543 // if we were not the master, reset our status too
3544 // this is required because most probably the mouse
3545 // is over this chart, blocking it from auto-refreshing
3546 if(master === false && (state.paused === true || state.selected === true))
3550 // get or create a chart state, given a DOM element
3551 NETDATA.chartState = function(element) {
3552 var state = $(element).data('netdata-state-object') || null;
3553 if(state === null) {
3554 state = new chartState(element);
3555 $(element).data('netdata-state-object', state);
3560 // ----------------------------------------------------------------------------------------------------------------
3561 // Library functions
3563 // Load a script without jquery
3564 // This is used to load jquery - after it is loaded, we use jquery
3565 NETDATA._loadjQuery = function(callback) {
3566 if(typeof jQuery === 'undefined') {
3567 if(NETDATA.options.debug.main_loop === true)
3568 console.log('loading ' + NETDATA.jQuery);
3570 var script = document.createElement('script');
3571 script.type = 'text/javascript';
3572 script.async = true;
3573 script.src = NETDATA.jQuery;
3575 // script.onabort = onError;
3576 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3577 if(typeof callback === "function")
3578 script.onload = callback;
3580 var s = document.getElementsByTagName('script')[0];
3581 s.parentNode.insertBefore(script, s);
3583 else if(typeof callback === "function")
3587 NETDATA._loadCSS = function(filename) {
3588 // don't use jQuery here
3589 // styles are loaded before jQuery
3590 // to eliminate showing an unstyled page to the user
3592 var fileref = document.createElement("link");
3593 fileref.setAttribute("rel", "stylesheet");
3594 fileref.setAttribute("type", "text/css");
3595 fileref.setAttribute("href", filename);
3597 if (typeof fileref !== 'undefined')
3598 document.getElementsByTagName("head")[0].appendChild(fileref);
3601 NETDATA.colorHex2Rgb = function(hex) {
3602 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3603 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3604 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3605 return r + r + g + g + b + b;
3608 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3610 r: parseInt(result[1], 16),
3611 g: parseInt(result[2], 16),
3612 b: parseInt(result[3], 16)
3616 NETDATA.colorLuminance = function(hex, lum) {
3617 // validate hex string
3618 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3620 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3624 // convert to decimal and change luminosity
3625 var rgb = "#", c, i;
3626 for (i = 0; i < 3; i++) {
3627 c = parseInt(hex.substr(i*2,2), 16);
3628 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3629 rgb += ("00"+c).substr(c.length);
3635 NETDATA.guid = function() {
3637 return Math.floor((1 + Math.random()) * 0x10000)
3642 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3645 NETDATA.zeropad = function(x) {
3646 if(x > -10 && x < 10) return '0' + x.toString();
3647 else return x.toString();
3650 // user function to signal us the DOM has been
3652 NETDATA.updatedDom = function() {
3653 NETDATA.options.updated_dom = true;
3656 NETDATA.ready = function(callback) {
3657 NETDATA.options.pauseCallback = callback;
3660 NETDATA.pause = function(callback) {
3661 if(typeof callback === 'function') {
3662 if (NETDATA.options.pause === true)
3665 NETDATA.options.pauseCallback = callback;
3669 NETDATA.unpause = function() {
3670 NETDATA.options.pauseCallback = null;
3671 NETDATA.options.updated_dom = true;
3672 NETDATA.options.pause = false;
3675 // ----------------------------------------------------------------------------------------------------------------
3677 // this is purely sequential charts refresher
3678 // it is meant to be autonomous
3679 NETDATA.chartRefresherNoParallel = function(index) {
3680 if(NETDATA.options.debug.main_loop === true)
3681 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3683 if(NETDATA.options.updated_dom === true) {
3684 // the dom has been updated
3685 // get the dom parts again
3686 NETDATA.parseDom(NETDATA.chartRefresher);
3689 if(index >= NETDATA.options.targets.length) {
3690 if(NETDATA.options.debug.main_loop === true)
3691 console.log('waiting to restart main loop...');
3693 NETDATA.options.auto_refresher_fast_weight = 0;
3695 setTimeout(function() {
3696 NETDATA.chartRefresher();
3697 }, NETDATA.options.current.idle_between_loops);
3700 var state = NETDATA.options.targets[index];
3702 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3703 if(NETDATA.options.debug.main_loop === true)
3704 console.log('fast rendering...');
3706 state.autoRefresh(function() {
3707 NETDATA.chartRefresherNoParallel(++index);
3711 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3712 NETDATA.options.auto_refresher_fast_weight = 0;
3714 setTimeout(function() {
3715 state.autoRefresh(function() {
3716 NETDATA.chartRefresherNoParallel(++index);
3718 }, NETDATA.options.current.idle_between_charts);
3723 NETDATA.chartRefresherWaitTime = function() {
3724 return NETDATA.options.current.idle_parallel_loops;
3727 // the default refresher
3728 NETDATA.chartRefresher = function() {
3729 // console.log('auto-refresher...');
3731 if(NETDATA.options.pause === true) {
3732 // console.log('auto-refresher is paused');
3733 setTimeout(NETDATA.chartRefresher,
3734 NETDATA.chartRefresherWaitTime());
3738 if(typeof NETDATA.options.pauseCallback === 'function') {
3739 // console.log('auto-refresher is calling pauseCallback');
3740 NETDATA.options.pause = true;
3741 NETDATA.options.pauseCallback();
3742 NETDATA.chartRefresher();
3746 if(NETDATA.options.current.parallel_refresher === false) {
3747 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3748 NETDATA.chartRefresherNoParallel(0);
3752 if(NETDATA.options.updated_dom === true) {
3753 // the dom has been updated
3754 // get the dom parts again
3755 // console.log('auto-refresher is calling parseDom()');
3756 NETDATA.parseDom(NETDATA.chartRefresher);
3761 var targets = NETDATA.options.targets;
3762 var len = targets.length;
3765 state = targets[len];
3766 if(state.isVisible() === false || state.running === true)
3769 if(state.library.initialized === false) {
3770 if(state.library.enabled === true) {
3771 state.library.initialize(NETDATA.chartRefresher);
3775 state.error('chart library "' + state.library_name + '" is not enabled.');
3779 parallel.unshift(state);
3782 if(parallel.length > 0) {
3783 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3784 // this will execute the jobs in parallel
3785 $(parallel).each(function() {
3790 // console.log('auto-refresher nothing to do');
3793 // run the next refresh iteration
3794 setTimeout(NETDATA.chartRefresher,
3795 NETDATA.chartRefresherWaitTime());
3798 NETDATA.parseDom = function(callback) {
3799 NETDATA.options.last_page_scroll = Date.now();
3800 NETDATA.options.updated_dom = false;
3802 var targets = $('div[data-netdata]'); //.filter(':visible');
3804 if(NETDATA.options.debug.main_loop === true)
3805 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3807 NETDATA.options.targets = [];
3808 var len = targets.length;
3810 // the initialization will take care of sizing
3811 // and the "loading..." message
3812 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3815 if(typeof callback === 'function')
3819 // this is the main function - where everything starts
3820 NETDATA.start = function() {
3821 // this should be called only once
3823 NETDATA.options.page_is_visible = true;
3825 $(window).blur(function() {
3826 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3827 NETDATA.options.page_is_visible = false;
3828 if(NETDATA.options.debug.focus === true)
3829 console.log('Lost Focus!');
3833 $(window).focus(function() {
3834 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3835 NETDATA.options.page_is_visible = true;
3836 if(NETDATA.options.debug.focus === true)
3837 console.log('Focus restored!');
3841 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3842 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3843 NETDATA.options.page_is_visible = false;
3844 if(NETDATA.options.debug.focus === true)
3845 console.log('Document has no focus!');
3849 // bootstrap tab switching
3850 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3852 // bootstrap modal switching
3853 var $modal = $('.modal');
3854 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3855 $modal.on('shown.bs.modal', NETDATA.onscroll);
3857 // bootstrap collapse switching
3858 var $collapse = $('.collapse');
3859 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3860 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3862 NETDATA.parseDom(NETDATA.chartRefresher);
3864 // Alarms initialization
3865 setTimeout(NETDATA.alarms.init, 1000);
3867 // Registry initialization
3868 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3870 if(typeof netdataCallback === 'function')
3874 // ----------------------------------------------------------------------------------------------------------------
3877 NETDATA.peityInitialize = function(callback) {
3878 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3880 url: NETDATA.peity_js,
3883 xhrFields: { withCredentials: true } // required for the cookie
3886 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3889 NETDATA.chartLibraries.peity.enabled = false;
3890 NETDATA.error(100, NETDATA.peity_js);
3892 .always(function() {
3893 if(typeof callback === "function")
3898 NETDATA.chartLibraries.peity.enabled = false;
3899 if(typeof callback === "function")
3904 NETDATA.peityChartUpdate = function(state, data) {
3905 state.peity_instance.innerHTML = data.result;
3907 if(state.peity_options.stroke !== state.chartColors()[0]) {
3908 state.peity_options.stroke = state.chartColors()[0];
3909 if(state.chart.chart_type === 'line')
3910 state.peity_options.fill = NETDATA.themes.current.background;
3912 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3915 $(state.peity_instance).peity('line', state.peity_options);
3919 NETDATA.peityChartCreate = function(state, data) {
3920 state.peity_instance = document.createElement('div');
3921 state.element_chart.appendChild(state.peity_instance);
3923 var self = $(state.element);
3924 state.peity_options = {
3925 stroke: NETDATA.themes.current.foreground,
3926 strokeWidth: self.data('peity-strokewidth') || 1,
3927 width: state.chartWidth(),
3928 height: state.chartHeight(),
3929 fill: NETDATA.themes.current.foreground
3932 NETDATA.peityChartUpdate(state, data);
3936 // ----------------------------------------------------------------------------------------------------------------
3939 NETDATA.sparklineInitialize = function(callback) {
3940 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3942 url: NETDATA.sparkline_js,
3945 xhrFields: { withCredentials: true } // required for the cookie
3948 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3951 NETDATA.chartLibraries.sparkline.enabled = false;
3952 NETDATA.error(100, NETDATA.sparkline_js);
3954 .always(function() {
3955 if(typeof callback === "function")
3960 NETDATA.chartLibraries.sparkline.enabled = false;
3961 if(typeof callback === "function")
3966 NETDATA.sparklineChartUpdate = function(state, data) {
3967 state.sparkline_options.width = state.chartWidth();
3968 state.sparkline_options.height = state.chartHeight();
3970 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3974 NETDATA.sparklineChartCreate = function(state, data) {
3975 var self = $(state.element);
3976 var type = self.data('sparkline-type') || 'line';
3977 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3978 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3979 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3980 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3981 var composite = self.data('sparkline-composite') || undefined;
3982 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3983 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3984 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3985 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3986 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3987 var spotColor = self.data('sparkline-spotcolor') || undefined;
3988 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3989 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3990 var spotRadius = self.data('sparkline-spotradius') || undefined;
3991 var valueSpots = self.data('sparkline-valuespots') || undefined;
3992 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3993 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3994 var lineWidth = self.data('sparkline-linewidth') || undefined;
3995 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3996 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3997 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3998 var xvalues = self.data('sparkline-xvalues') || undefined;
3999 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
4000 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
4001 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
4002 var disableInteraction = self.data('sparkline-disableinteraction') || false;
4003 var disableTooltips = self.data('sparkline-disabletooltips') || false;
4004 var disableHighlight = self.data('sparkline-disablehighlight') || false;
4005 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
4006 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
4007 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
4008 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
4009 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
4010 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
4011 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
4012 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
4013 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
4014 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
4015 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
4016 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
4017 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
4018 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
4019 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
4020 var animatedZooms = self.data('sparkline-animatedzooms') || false;
4022 if(spotColor === 'disable') spotColor='';
4023 if(minSpotColor === 'disable') minSpotColor='';
4024 if(maxSpotColor === 'disable') maxSpotColor='';
4026 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
4028 state.sparkline_options = {
4030 lineColor: lineColor,
4031 fillColor: fillColor,
4032 chartRangeMin: chartRangeMin,
4033 chartRangeMax: chartRangeMax,
4034 composite: composite,
4035 enableTagOptions: enableTagOptions,
4036 tagOptionPrefix: tagOptionPrefix,
4037 tagValuesAttribute: tagValuesAttribute,
4038 disableHiddenCheck: disableHiddenCheck,
4039 defaultPixelsPerValue: defaultPixelsPerValue,
4040 spotColor: spotColor,
4041 minSpotColor: minSpotColor,
4042 maxSpotColor: maxSpotColor,
4043 spotRadius: spotRadius,
4044 valueSpots: valueSpots,
4045 highlightSpotColor: highlightSpotColor,
4046 highlightLineColor: highlightLineColor,
4047 lineWidth: lineWidth,
4048 normalRangeMin: normalRangeMin,
4049 normalRangeMax: normalRangeMax,
4050 drawNormalOnTop: drawNormalOnTop,
4052 chartRangeClip: chartRangeClip,
4053 chartRangeMinX: chartRangeMinX,
4054 chartRangeMaxX: chartRangeMaxX,
4055 disableInteraction: disableInteraction,
4056 disableTooltips: disableTooltips,
4057 disableHighlight: disableHighlight,
4058 highlightLighten: highlightLighten,
4059 highlightColor: highlightColor,
4060 tooltipContainer: tooltipContainer,
4061 tooltipClassname: tooltipClassname,
4062 tooltipChartTitle: state.title,
4063 tooltipFormat: tooltipFormat,
4064 tooltipPrefix: tooltipPrefix,
4065 tooltipSuffix: tooltipSuffix,
4066 tooltipSkipNull: tooltipSkipNull,
4067 tooltipValueLookups: tooltipValueLookups,
4068 tooltipFormatFieldlist: tooltipFormatFieldlist,
4069 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4070 numberFormatter: numberFormatter,
4071 numberDigitGroupSep: numberDigitGroupSep,
4072 numberDecimalMark: numberDecimalMark,
4073 numberDigitGroupCount: numberDigitGroupCount,
4074 animatedZooms: animatedZooms,
4075 width: state.chartWidth(),
4076 height: state.chartHeight()
4079 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4083 // ----------------------------------------------------------------------------------------------------------------
4090 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4091 if(after < state.netdata_first)
4092 after = state.netdata_first;
4094 if(before > state.netdata_last)
4095 before = state.netdata_last;
4097 state.setMode('zoom');
4098 state.globalSelectionSyncStop();
4099 state.globalSelectionSyncDelay();
4100 state.dygraph_user_action = true;
4101 state.dygraph_force_zoom = true;
4102 state.updateChartPanOrZoom(after, before);
4103 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4106 NETDATA.dygraphSetSelection = function(state, t) {
4107 if(typeof state.dygraph_instance !== 'undefined') {
4108 var r = state.calculateRowForTime(t);
4110 state.dygraph_instance.setSelection(r);
4112 state.dygraph_instance.clearSelection();
4113 state.legendShowUndefined();
4120 NETDATA.dygraphClearSelection = function(state) {
4121 if(typeof state.dygraph_instance !== 'undefined') {
4122 state.dygraph_instance.clearSelection();
4127 NETDATA.dygraphSmoothInitialize = function(callback) {
4129 url: NETDATA.dygraph_smooth_js,
4132 xhrFields: { withCredentials: true } // required for the cookie
4135 NETDATA.dygraph.smooth = true;
4136 smoothPlotter.smoothing = 0.3;
4139 NETDATA.dygraph.smooth = false;
4141 .always(function() {
4142 if(typeof callback === "function")
4147 NETDATA.dygraphInitialize = function(callback) {
4148 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4150 url: NETDATA.dygraph_js,
4153 xhrFields: { withCredentials: true } // required for the cookie
4156 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4159 NETDATA.chartLibraries.dygraph.enabled = false;
4160 NETDATA.error(100, NETDATA.dygraph_js);
4162 .always(function() {
4163 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4164 NETDATA.dygraphSmoothInitialize(callback);
4165 else if(typeof callback === "function")
4170 NETDATA.chartLibraries.dygraph.enabled = false;
4171 if(typeof callback === "function")
4176 NETDATA.dygraphChartUpdate = function(state, data) {
4177 var dygraph = state.dygraph_instance;
4179 if(typeof dygraph === 'undefined')
4180 return NETDATA.dygraphChartCreate(state, data);
4182 // when the chart is not visible, and hidden
4183 // if there is a window resize, dygraph detects
4184 // its element size as 0x0.
4185 // this will make it re-appear properly
4187 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4191 file: data.result.data,
4192 colors: state.chartColors(),
4193 labels: data.result.labels,
4194 labelsDivWidth: state.chartWidth() - 70,
4195 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4198 if(state.dygraph_force_zoom === true) {
4199 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4200 state.log('dygraphChartUpdate() forced zoom update');
4202 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4203 options.isZoomedIgnoreProgrammaticZoom = true;
4204 state.dygraph_force_zoom = false;
4206 else if(state.current.name !== 'auto') {
4207 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4208 state.log('dygraphChartUpdate() loose update');
4211 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4212 state.log('dygraphChartUpdate() strict update');
4214 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4215 options.isZoomedIgnoreProgrammaticZoom = true;
4218 options.valueRange = state.dygraph_options.valueRange;
4220 var oldMax = null, oldMin = null;
4221 if(state.__commonMin !== null) {
4222 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4223 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4225 if(state.__commonMax !== null) {
4226 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4227 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4230 if(state.dygraph_smooth_eligible === true) {
4231 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4232 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4233 NETDATA.dygraphChartCreate(state, data);
4238 dygraph.updateOptions(options);
4241 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4242 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4243 options.valueRange[0] = NETDATA.commonMin.get(state);
4246 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4247 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4248 options.valueRange[1] = NETDATA.commonMax.get(state);
4252 if(redraw === true) {
4253 // state.log('forcing redraw to adapt to common- min/max');
4254 dygraph.updateOptions(options);
4257 // decide the decimal points on the legend of the chart
4258 state.legendFormatValueDecimalsFromMinMax(
4259 state.dygraph_instance.axes_[0].extremeRange[0],
4260 state.dygraph_instance.axes_[0].extremeRange[1]
4263 state.dygraph_last_rendered = Date.now();
4267 NETDATA.dygraphChartCreate = function(state, data) {
4268 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4269 state.log('dygraphChartCreate()');
4271 var self = $(state.element);
4273 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4274 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4276 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4278 var smooth = (NETDATA.dygraph.smooth === true)
4279 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4282 state.dygraph_options = {
4283 colors: self.data('dygraph-colors') || state.chartColors(),
4285 // leave a few pixels empty on the right of the chart
4286 rightGap: self.data('dygraph-rightgap')
4289 showRangeSelector: self.data('dygraph-showrangeselector')
4292 showRoller: self.data('dygraph-showroller')
4295 title: self.data('dygraph-title')
4298 titleHeight: self.data('dygraph-titleheight')
4301 legend: self.data('dygraph-legend')
4302 || 'always', // we need this to get selection events
4304 labels: data.result.labels,
4306 labelsDiv: self.data('dygraph-labelsdiv')
4307 || state.element_legend_childs.hidden,
4309 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4310 || { 'fontSize':'1px' },
4312 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4313 || state.chartWidth() - 70,
4315 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4318 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4324 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4327 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4330 includeZero: self.data('dygraph-includezero')
4331 || (chart_type === 'stacked'),
4333 xRangePad: self.data('dygraph-xrangepad')
4336 yRangePad: self.data('dygraph-yrangepad')
4339 valueRange: self.data('dygraph-valuerange')
4342 ylabel: state.units,
4344 yLabelWidth: self.data('dygraph-ylabelwidth')
4347 // the function to plot the chart
4350 // The width of the lines connecting data points.
4351 // This can be used to increase the contrast or some graphs.
4352 strokeWidth: self.data('dygraph-strokewidth')
4353 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4355 strokePattern: self.data('dygraph-strokepattern')
4358 // The size of the dot to draw on each point in pixels (see drawPoints).
4359 // A dot is always drawn when a point is "isolated",
4360 // i.e. there is a missing point on either side of it.
4361 // This also controls the size of those dots.
4362 drawPoints: self.data('dygraph-drawpoints')
4365 // Draw points at the edges of gaps in the data.
4366 // This improves visibility of small data segments or other data irregularities.
4367 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4370 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4373 pointSize: self.data('dygraph-pointsize')
4376 // enabling this makes the chart with little square lines
4377 stepPlot: self.data('dygraph-stepplot')
4380 // Draw a border around graph lines to make crossing lines more easily
4381 // distinguishable. Useful for graphs with many lines.
4382 strokeBorderColor: self.data('dygraph-strokebordercolor')
4383 || NETDATA.themes.current.background,
4385 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4386 || (chart_type === 'stacked')?0.0:0.0,
4388 fillGraph: self.data('dygraph-fillgraph')
4389 || (chart_type === 'area' || chart_type === 'stacked'),
4391 fillAlpha: self.data('dygraph-fillalpha')
4392 || ((chart_type === 'stacked')
4393 ?NETDATA.options.current.color_fill_opacity_stacked
4394 :NETDATA.options.current.color_fill_opacity_area),
4396 stackedGraph: self.data('dygraph-stackedgraph')
4397 || (chart_type === 'stacked'),
4399 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4402 drawAxis: self.data('dygraph-drawaxis')
4405 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4408 axisLineColor: self.data('dygraph-axislinecolor')
4409 || NETDATA.themes.current.axis,
4411 axisLineWidth: self.data('dygraph-axislinewidth')
4414 drawGrid: self.data('dygraph-drawgrid')
4417 gridLinePattern: self.data('dygraph-gridlinepattern')
4420 gridLineWidth: self.data('dygraph-gridlinewidth')
4423 gridLineColor: self.data('dygraph-gridlinecolor')
4424 || NETDATA.themes.current.grid,
4426 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4429 sigFigs: self.data('dygraph-sigfigs')
4432 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4435 valueFormatter: self.data('dygraph-valueformatter')
4438 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4439 || highlightCircleSize,
4441 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4442 || null, // TOO SLOW: { strokeWidth: 1.5 },
4444 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4445 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4447 pointClickCallback: self.data('dygraph-pointclickcallback')
4450 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4455 ticker: Dygraph.dateTicker,
4456 axisLabelFormatter: function (d, gran) {
4458 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4463 axisLabelFormatter: function (y) {
4465 // unfortunately, we have to call this every single time
4466 state.legendFormatValueDecimalsFromMinMax(
4467 this.axes_[0].extremeRange[0],
4468 this.axes_[0].extremeRange[1]
4471 return state.legendFormatValue(y);
4475 legendFormatter: function(data) {
4476 var elements = state.element_legend_childs;
4478 // if the hidden div is not there
4479 // we are not managing the legend
4480 if(elements.hidden === null) return;
4482 if (typeof data.x !== 'undefined') {
4483 state.legendSetDate(data.x);
4484 var i = data.series.length;
4486 var series = data.series[i];
4487 if(series.isVisible === true)
4488 state.legendSetLabelValue(series.label, series.y);
4490 state.legendSetLabelValue(series.label, null);
4496 drawCallback: function(dygraph, is_initial) {
4497 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4498 state.dygraph_user_action = false;
4500 var x_range = dygraph.xAxisRange();
4501 var after = Math.round(x_range[0]);
4502 var before = Math.round(x_range[1]);
4504 if(NETDATA.options.debug.dygraph === true)
4505 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4507 if(before <= state.netdata_last && after >= state.netdata_first)
4508 state.updateChartPanOrZoom(after, before);
4511 zoomCallback: function(minDate, maxDate, yRanges) {
4514 if(NETDATA.options.debug.dygraph === true)
4515 state.log('dygraphZoomCallback()');
4517 state.globalSelectionSyncStop();
4518 state.globalSelectionSyncDelay();
4519 state.setMode('zoom');
4521 // refresh it to the greatest possible zoom level
4522 state.dygraph_user_action = true;
4523 state.dygraph_force_zoom = true;
4524 state.updateChartPanOrZoom(minDate, maxDate);
4526 highlightCallback: function(event, x, points, row, seriesName) {
4529 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4530 state.log('dygraphHighlightCallback()');
4534 // there is a bug in dygraph when the chart is zoomed enough
4535 // the time it thinks is selected is wrong
4536 // here we calculate the time t based on the row number selected
4538 // var t = state.data_after + row * state.data_update_every;
4539 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
4541 state.globalSelectionSync(x);
4543 // fix legend zIndex using the internal structures of dygraph legend module
4544 // this works, but it is a hack!
4545 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4547 unhighlightCallback: function(event) {
4550 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4551 state.log('dygraphUnhighlightCallback()');
4553 state.unpauseChart();
4554 state.globalSelectionSyncStop();
4556 interactionModel : {
4557 mousedown: function(event, dygraph, context) {
4558 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4559 state.log('interactionModel.mousedown()');
4561 state.dygraph_user_action = true;
4562 state.globalSelectionSyncStop();
4564 if(NETDATA.options.debug.dygraph === true)
4565 state.log('dygraphMouseDown()');
4567 // Right-click should not initiate a zoom.
4568 if(event.button && event.button === 2) return;
4570 context.initializeMouseDown(event, dygraph, context);
4572 if(event.button && event.button === 1) {
4573 if (event.altKey || event.shiftKey) {
4574 state.setMode('pan');
4575 state.globalSelectionSyncDelay();
4576 Dygraph.startPan(event, dygraph, context);
4579 state.setMode('zoom');
4580 state.globalSelectionSyncDelay();
4581 Dygraph.startZoom(event, dygraph, context);
4585 if (event.altKey || event.shiftKey) {
4586 state.setMode('zoom');
4587 state.globalSelectionSyncDelay();
4588 Dygraph.startZoom(event, dygraph, context);
4591 state.setMode('pan');
4592 state.globalSelectionSyncDelay();
4593 Dygraph.startPan(event, dygraph, context);
4597 mousemove: function(event, dygraph, context) {
4598 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4599 state.log('interactionModel.mousemove()');
4601 if(context.isPanning) {
4602 state.dygraph_user_action = true;
4603 state.globalSelectionSyncStop();
4604 state.globalSelectionSyncDelay();
4605 state.setMode('pan');
4606 context.is2DPan = false;
4607 Dygraph.movePan(event, dygraph, context);
4609 else if(context.isZooming) {
4610 state.dygraph_user_action = true;
4611 state.globalSelectionSyncStop();
4612 state.globalSelectionSyncDelay();
4613 state.setMode('zoom');
4614 Dygraph.moveZoom(event, dygraph, context);
4617 mouseup: function(event, dygraph, context) {
4618 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4619 state.log('interactionModel.mouseup()');
4621 if (context.isPanning) {
4622 state.dygraph_user_action = true;
4623 state.globalSelectionSyncDelay();
4624 Dygraph.endPan(event, dygraph, context);
4626 else if (context.isZooming) {
4627 state.dygraph_user_action = true;
4628 state.globalSelectionSyncDelay();
4629 Dygraph.endZoom(event, dygraph, context);
4632 click: function(event, dygraph, context) {
4636 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4637 state.log('interactionModel.click()');
4639 event.preventDefault();
4641 dblclick: function(event, dygraph, context) {
4646 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4647 state.log('interactionModel.dblclick()');
4648 NETDATA.resetAllCharts(state);
4650 wheel: function(event, dygraph, context) {
4653 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4654 state.log('interactionModel.wheel()');
4656 // Take the offset of a mouse event on the dygraph canvas and
4657 // convert it to a pair of percentages from the bottom left.
4658 // (Not top left, bottom is where the lower value is.)
4659 function offsetToPercentage(g, offsetX, offsetY) {
4660 // This is calculating the pixel offset of the leftmost date.
4661 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4662 var yar0 = g.yAxisRange(0);
4664 // This is calculating the pixel of the highest value. (Top pixel)
4665 var yOffset = g.toDomCoords(null, yar0[1])[1];
4667 // x y w and h are relative to the corner of the drawing area,
4668 // so that the upper corner of the drawing area is (0, 0).
4669 var x = offsetX - xOffset;
4670 var y = offsetY - yOffset;
4672 // This is computing the rightmost pixel, effectively defining the
4674 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4676 // This is computing the lowest pixel, effectively defining the height.
4677 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4679 // Percentage from the left.
4680 var xPct = w === 0 ? 0 : (x / w);
4681 // Percentage from the top.
4682 var yPct = h === 0 ? 0 : (y / h);
4684 // The (1-) part below changes it from "% distance down from the top"
4685 // to "% distance up from the bottom".
4686 return [xPct, (1-yPct)];
4689 // Adjusts [x, y] toward each other by zoomInPercentage%
4690 // Split it so the left/bottom axis gets xBias/yBias of that change and
4691 // tight/top gets (1-xBias)/(1-yBias) of that change.
4693 // If a bias is missing it splits it down the middle.
4694 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4695 xBias = xBias || 0.5;
4696 yBias = yBias || 0.5;
4698 function adjustAxis(axis, zoomInPercentage, bias) {
4699 var delta = axis[1] - axis[0];
4700 var increment = delta * zoomInPercentage;
4701 var foo = [increment * bias, increment * (1-bias)];
4703 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4706 var yAxes = g.yAxisRanges();
4708 for (var i = 0; i < yAxes.length; i++) {
4709 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4712 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4715 if(event.altKey || event.shiftKey) {
4716 state.dygraph_user_action = true;
4718 state.globalSelectionSyncStop();
4719 state.globalSelectionSyncDelay();
4721 // http://dygraphs.com/gallery/interaction-api.js
4723 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4725 normal_def = event.wheelDelta / 40;
4728 normal_def = event.deltaY * -1.2;
4730 var normal = (event.detail) ? event.detail * -1 : normal_def;
4731 var percentage = normal / 50;
4733 if (!(event.offsetX && event.offsetY)){
4734 event.offsetX = event.layerX - event.target.offsetLeft;
4735 event.offsetY = event.layerY - event.target.offsetTop;
4738 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4739 var xPct = percentages[0];
4740 var yPct = percentages[1];
4742 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4743 var after = new_x_range[0];
4744 var before = new_x_range[1];
4746 var first = state.netdata_first + state.data_update_every;
4747 var last = state.netdata_last + state.data_update_every;
4750 after -= (before - last);
4757 state.setMode('zoom');
4758 if(state.updateChartPanOrZoom(after, before) === true)
4759 dygraph.updateOptions({ dateWindow: [ after, before ] });
4761 event.preventDefault();
4764 touchstart: function(event, dygraph, context) {
4765 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4766 state.log('interactionModel.touchstart()');
4768 state.dygraph_user_action = true;
4769 state.setMode('zoom');
4772 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4774 // we overwrite the touch directions at the end, to overwrite
4775 // the internal default of dygraph
4776 context.touchDirections = { x: true, y: false };
4778 state.dygraph_last_touch_start = Date.now();
4779 state.dygraph_last_touch_move = 0;
4781 if(typeof event.touches[0].pageX === 'number')
4782 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4784 state.dygraph_last_touch_page_x = 0;
4786 touchmove: function(event, dygraph, context) {
4787 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4788 state.log('interactionModel.touchmove()');
4790 state.dygraph_user_action = true;
4791 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4793 state.dygraph_last_touch_move = Date.now();
4795 touchend: function(event, dygraph, context) {
4796 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4797 state.log('interactionModel.touchend()');
4799 state.dygraph_user_action = true;
4800 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4802 // if it didn't move, it is a selection
4803 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4804 // internal api of dygraph
4805 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4806 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4807 if(NETDATA.dygraphSetSelection(state, t) === true)
4808 state.globalSelectionSync(t);
4811 // if it was double tap within double click time, reset the charts
4812 var now = Date.now();
4813 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4814 if(state.dygraph_last_touch_move === 0) {
4815 var dt = now - state.dygraph_last_touch_end;
4816 if(dt <= NETDATA.options.current.double_click_speed)
4817 NETDATA.resetAllCharts(state);
4821 // remember the timestamp of the last touch end
4822 state.dygraph_last_touch_end = now;
4827 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4828 state.dygraph_options.drawGrid = false;
4829 state.dygraph_options.drawAxis = false;
4830 state.dygraph_options.title = undefined;
4831 state.dygraph_options.ylabel = undefined;
4832 state.dygraph_options.yLabelWidth = 0;
4833 state.dygraph_options.labelsDivWidth = 120;
4834 state.dygraph_options.labelsDivStyles.width = '120px';
4835 state.dygraph_options.labelsSeparateLines = true;
4836 state.dygraph_options.rightGap = 0;
4837 state.dygraph_options.yRangePad = 1;
4840 if(smooth === true) {
4841 state.dygraph_smooth_eligible = true;
4843 if(NETDATA.options.current.smooth_plot === true)
4844 state.dygraph_options.plotter = smoothPlotter;
4846 else state.dygraph_smooth_eligible = false;
4848 state.dygraph_instance = new Dygraph(state.element_chart,
4849 data.result.data, state.dygraph_options);
4851 state.dygraph_force_zoom = false;
4852 state.dygraph_user_action = false;
4853 state.dygraph_last_rendered = Date.now();
4855 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4856 state.__commonMin = self.data('common-min') || null;
4857 state.__commonMax = self.data('common-max') || null;
4860 state.log('incompatible version of Dygraph detected');
4861 state.__commonMin = null;
4862 state.__commonMax = null;
4865 // decide the decimal points on the legend of the chart
4866 state.legendFormatValueDecimalsFromMinMax(
4867 state.dygraph_instance.axes_[0].extremeRange[0],
4868 state.dygraph_instance.axes_[0].extremeRange[1]
4874 // ----------------------------------------------------------------------------------------------------------------
4877 NETDATA.morrisInitialize = function(callback) {
4878 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4880 // morris requires raphael
4881 if(!NETDATA.chartLibraries.raphael.initialized) {
4882 if(NETDATA.chartLibraries.raphael.enabled) {
4883 NETDATA.raphaelInitialize(function() {
4884 NETDATA.morrisInitialize(callback);
4888 NETDATA.chartLibraries.morris.enabled = false;
4889 if(typeof callback === "function")
4894 NETDATA._loadCSS(NETDATA.morris_css);
4897 url: NETDATA.morris_js,
4900 xhrFields: { withCredentials: true } // required for the cookie
4903 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4906 NETDATA.chartLibraries.morris.enabled = false;
4907 NETDATA.error(100, NETDATA.morris_js);
4909 .always(function() {
4910 if(typeof callback === "function")
4916 NETDATA.chartLibraries.morris.enabled = false;
4917 if(typeof callback === "function")
4922 NETDATA.morrisChartUpdate = function(state, data) {
4923 state.morris_instance.setData(data.result.data);
4927 NETDATA.morrisChartCreate = function(state, data) {
4929 state.morris_options = {
4930 element: state.element_chart.id,
4931 data: data.result.data,
4933 ykeys: data.dimension_names,
4934 labels: data.dimension_names,
4940 continuousLine: false,
4941 behaveLikeLine: false
4944 if(state.chart.chart_type === 'line')
4945 state.morris_instance = new Morris.Line(state.morris_options);
4947 else if(state.chart.chart_type === 'area') {
4948 state.morris_options.behaveLikeLine = true;
4949 state.morris_instance = new Morris.Area(state.morris_options);
4952 state.morris_instance = new Morris.Area(state.morris_options);
4957 // ----------------------------------------------------------------------------------------------------------------
4960 NETDATA.raphaelInitialize = function(callback) {
4961 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4963 url: NETDATA.raphael_js,
4966 xhrFields: { withCredentials: true } // required for the cookie
4969 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4972 NETDATA.chartLibraries.raphael.enabled = false;
4973 NETDATA.error(100, NETDATA.raphael_js);
4975 .always(function() {
4976 if(typeof callback === "function")
4981 NETDATA.chartLibraries.raphael.enabled = false;
4982 if(typeof callback === "function")
4987 NETDATA.raphaelChartUpdate = function(state, data) {
4988 $(state.element_chart).raphael(data.result, {
4989 width: state.chartWidth(),
4990 height: state.chartHeight()
4996 NETDATA.raphaelChartCreate = function(state, data) {
4997 $(state.element_chart).raphael(data.result, {
4998 width: state.chartWidth(),
4999 height: state.chartHeight()
5005 // ----------------------------------------------------------------------------------------------------------------
5008 NETDATA.c3Initialize = function(callback) {
5009 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
5012 if(!NETDATA.chartLibraries.d3.initialized) {
5013 if(NETDATA.chartLibraries.d3.enabled) {
5014 NETDATA.d3Initialize(function() {
5015 NETDATA.c3Initialize(callback);
5019 NETDATA.chartLibraries.c3.enabled = false;
5020 if(typeof callback === "function")
5025 NETDATA._loadCSS(NETDATA.c3_css);
5031 xhrFields: { withCredentials: true } // required for the cookie
5034 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
5037 NETDATA.chartLibraries.c3.enabled = false;
5038 NETDATA.error(100, NETDATA.c3_js);
5040 .always(function() {
5041 if(typeof callback === "function")
5047 NETDATA.chartLibraries.c3.enabled = false;
5048 if(typeof callback === "function")
5053 NETDATA.c3ChartUpdate = function(state, data) {
5054 state.c3_instance.destroy();
5055 return NETDATA.c3ChartCreate(state, data);
5057 //state.c3_instance.load({
5058 // rows: data.result,
5065 NETDATA.c3ChartCreate = function(state, data) {
5067 state.element_chart.id = 'c3-' + state.uuid;
5068 // console.log('id = ' + state.element_chart.id);
5070 state.c3_instance = c3.generate({
5071 bindto: '#' + state.element_chart.id,
5073 width: state.chartWidth(),
5074 height: state.chartHeight()
5077 pattern: state.chartColors()
5082 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5088 format: function(x) {
5089 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5116 // console.log(state.c3_instance);
5121 // ----------------------------------------------------------------------------------------------------------------
5124 NETDATA.d3Initialize = function(callback) {
5125 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5130 xhrFields: { withCredentials: true } // required for the cookie
5133 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5136 NETDATA.chartLibraries.d3.enabled = false;
5137 NETDATA.error(100, NETDATA.d3_js);
5139 .always(function() {
5140 if(typeof callback === "function")
5145 NETDATA.chartLibraries.d3.enabled = false;
5146 if(typeof callback === "function")
5151 NETDATA.d3ChartUpdate = function(state, data) {
5158 NETDATA.d3ChartCreate = function(state, data) {
5165 // ----------------------------------------------------------------------------------------------------------------
5168 NETDATA.googleInitialize = function(callback) {
5169 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5171 url: NETDATA.google_js,
5174 xhrFields: { withCredentials: true } // required for the cookie
5177 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5178 google.load('visualization', '1.1', {
5179 'packages': ['corechart', 'controls'],
5180 'callback': callback
5184 NETDATA.chartLibraries.google.enabled = false;
5185 NETDATA.error(100, NETDATA.google_js);
5186 if(typeof callback === "function")
5191 NETDATA.chartLibraries.google.enabled = false;
5192 if(typeof callback === "function")
5197 NETDATA.googleChartUpdate = function(state, data) {
5198 var datatable = new google.visualization.DataTable(data.result);
5199 state.google_instance.draw(datatable, state.google_options);
5203 NETDATA.googleChartCreate = function(state, data) {
5204 var datatable = new google.visualization.DataTable(data.result);
5206 state.google_options = {
5207 colors: state.chartColors(),
5209 // do not set width, height - the chart resizes itself
5210 //width: state.chartWidth(),
5211 //height: state.chartHeight(),
5216 // title: "Time of Day",
5217 // format:'HH:mm:ss',
5218 viewWindowMode: 'maximized',
5230 viewWindowMode: 'pretty',
5245 focusTarget: 'category',
5252 titlePosition: 'out',
5263 curveType: 'function',
5268 switch(state.chart.chart_type) {
5270 state.google_options.vAxis.viewWindowMode = 'maximized';
5271 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5272 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5276 state.google_options.isStacked = true;
5277 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5278 state.google_options.vAxis.viewWindowMode = 'maximized';
5279 state.google_options.vAxis.minValue = null;
5280 state.google_options.vAxis.maxValue = null;
5281 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5286 state.google_options.lineWidth = 2;
5287 state.google_instance = new google.visualization.LineChart(state.element_chart);
5291 state.google_instance.draw(datatable, state.google_options);
5295 // ----------------------------------------------------------------------------------------------------------------
5297 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5298 if(typeof value !== 'number') value = 0;
5299 if(typeof min !== 'number') min = 0;
5300 if(typeof max !== 'number') max = 0;
5302 if(min > value) min = value;
5303 if(max < value) max = value;
5305 // make sure it is zero based
5306 if(min > 0) min = 0;
5307 if(max < 0) max = 0;
5312 pcent = Math.round(value * 100 / max);
5313 if(pcent === 0) pcent = 0.1;
5317 pcent = Math.round(-value * 100 / min);
5318 if(pcent === 0) pcent = -0.1;
5324 // ----------------------------------------------------------------------------------------------------------------
5327 NETDATA.easypiechartInitialize = function(callback) {
5328 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5330 url: NETDATA.easypiechart_js,
5333 xhrFields: { withCredentials: true } // required for the cookie
5336 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5339 NETDATA.chartLibraries.easypiechart.enabled = false;
5340 NETDATA.error(100, NETDATA.easypiechart_js);
5342 .always(function() {
5343 if(typeof callback === "function")
5348 NETDATA.chartLibraries.easypiechart.enabled = false;
5349 if(typeof callback === "function")
5354 NETDATA.easypiechartClearSelection = function(state) {
5355 if(typeof state.easyPieChartEvent !== 'undefined') {
5356 if(state.easyPieChartEvent.timer !== undefined) {
5357 clearTimeout(state.easyPieChartEvent.timer);
5360 state.easyPieChartEvent.timer = undefined;
5363 if(state.isAutoRefreshable() === true && state.data !== null) {
5364 NETDATA.easypiechartChartUpdate(state, state.data);
5367 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5368 state.easyPieChart_instance.update(0);
5370 state.easyPieChart_instance.enableAnimation();
5375 NETDATA.easypiechartSetSelection = function(state, t) {
5376 if(state.timeIsVisible(t) !== true)
5377 return NETDATA.easypiechartClearSelection(state);
5379 var slot = state.calculateRowForTime(t);
5380 if(slot < 0 || slot >= state.data.result.length)
5381 return NETDATA.easypiechartClearSelection(state);
5383 if(typeof state.easyPieChartEvent === 'undefined') {
5384 state.easyPieChartEvent = {
5391 var value = state.data.result[state.data.result.length - 1 - slot];
5392 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5393 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5394 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5396 state.easyPieChartEvent.value = value;
5397 state.easyPieChartEvent.pcent = pcent;
5398 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5400 if(state.easyPieChartEvent.timer === undefined) {
5401 state.easyPieChart_instance.disableAnimation();
5403 state.easyPieChartEvent.timer = setTimeout(function() {
5404 state.easyPieChartEvent.timer = undefined;
5405 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5406 }, NETDATA.options.current.charts_selection_animation_delay);
5412 NETDATA.easypiechartChartUpdate = function(state, data) {
5413 var value, min, max, pcent;
5415 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5420 value = data.result[0];
5421 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5422 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5423 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5426 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5427 state.easyPieChart_instance.update(pcent);
5431 NETDATA.easypiechartChartCreate = function(state, data) {
5432 var self = $(state.element);
5433 var chart = $(state.element_chart);
5435 var value = data.result[0];
5436 var min = self.data('easypiechart-min-value') || null;
5437 var max = self.data('easypiechart-max-value') || null;
5438 var adjust = self.data('easypiechart-adjust') || null;
5441 min = NETDATA.commonMin.get(state);
5442 state.easyPieChartMin = null;
5445 state.easyPieChartMin = min;
5448 max = NETDATA.commonMax.get(state);
5449 state.easyPieChartMax = null;
5452 state.easyPieChartMax = max;
5454 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5456 chart.data('data-percent', pcent);
5460 case 'width': size = state.chartHeight(); break;
5461 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5462 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5464 default: size = state.chartWidth(); break;
5466 state.element.style.width = size + 'px';
5467 state.element.style.height = size + 'px';
5469 var stroke = Math.floor(size / 22);
5470 if(stroke < 3) stroke = 2;
5472 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5473 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5474 state.easyPieChartLabel = document.createElement('span');
5475 state.easyPieChartLabel.className = 'easyPieChartLabel';
5476 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5477 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5478 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5479 state.element_chart.appendChild(state.easyPieChartLabel);
5481 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5482 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5483 state.easyPieChartTitle = document.createElement('span');
5484 state.easyPieChartTitle.className = 'easyPieChartTitle';
5485 state.easyPieChartTitle.innerText = state.title;
5486 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5487 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5488 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5489 state.element_chart.appendChild(state.easyPieChartTitle);
5491 var unitfontsize = Math.round(titlefontsize * 0.9);
5492 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5493 state.easyPieChartUnits = document.createElement('span');
5494 state.easyPieChartUnits.className = 'easyPieChartUnits';
5495 state.easyPieChartUnits.innerText = state.units;
5496 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5497 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5498 state.element_chart.appendChild(state.easyPieChartUnits);
5500 var barColor = self.data('easypiechart-barcolor');
5501 if(typeof barColor === 'undefined' || barColor === null)
5502 barColor = state.chartColors()[0];
5504 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5505 var tmp = eval(barColor);
5506 if(typeof tmp === 'function')
5510 chart.easyPieChart({
5512 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5513 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5514 scaleLength: self.data('easypiechart-scalelength') || 5,
5515 lineCap: self.data('easypiechart-linecap') || 'round',
5516 lineWidth: self.data('easypiechart-linewidth') || stroke,
5517 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5518 size: self.data('easypiechart-size') || size,
5519 rotate: self.data('easypiechart-rotate') || 0,
5520 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5521 easing: self.data('easypiechart-easing') || undefined
5524 // when we just re-create the chart
5525 // do not animate the first update
5527 if(typeof state.easyPieChart_instance !== 'undefined')
5530 state.easyPieChart_instance = chart.data('easyPieChart');
5531 if(animate === false) state.easyPieChart_instance.disableAnimation();
5532 state.easyPieChart_instance.update(pcent);
5533 if(animate === false) state.easyPieChart_instance.enableAnimation();
5537 // ----------------------------------------------------------------------------------------------------------------
5540 NETDATA.gaugeInitialize = function(callback) {
5541 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5543 url: NETDATA.gauge_js,
5546 xhrFields: { withCredentials: true } // required for the cookie
5549 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5552 NETDATA.chartLibraries.gauge.enabled = false;
5553 NETDATA.error(100, NETDATA.gauge_js);
5555 .always(function() {
5556 if(typeof callback === "function")
5561 NETDATA.chartLibraries.gauge.enabled = false;
5562 if(typeof callback === "function")
5567 NETDATA.gaugeAnimation = function(state, status) {
5570 if(typeof status === 'boolean' && status === false)
5572 else if(typeof status === 'number')
5575 // console.log('gauge speed ' + speed);
5576 state.gauge_instance.animationSpeed = speed;
5577 state.___gaugeOld__.speed = speed;
5580 NETDATA.gaugeSet = function(state, value, min, max) {
5581 if(typeof value !== 'number') value = 0;
5582 if(typeof min !== 'number') min = 0;
5583 if(typeof max !== 'number') max = 0;
5584 if(value > max) max = value;
5585 if(value < min) min = value;
5591 else if(min === max)
5594 // gauge.js has an issue if the needle
5595 // is smaller than min or larger than max
5596 // when we set the new values
5597 // the needle will go crazy
5599 // to prevent it, we always feed it
5600 // with a percentage, so that the needle
5601 // is always between min and max
5602 var pcent = (value - min) * 100 / (max - min);
5604 // bug fix for gauge.js 1.3.1
5605 // if the value is the absolute min or max, the chart is broken
5606 if(pcent < 0.001) pcent = 0.001;
5607 if(pcent > 99.999) pcent = 99.999;
5609 state.gauge_instance.set(pcent);
5610 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5612 state.___gaugeOld__.value = value;
5613 state.___gaugeOld__.min = min;
5614 state.___gaugeOld__.max = max;
5617 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5618 if(state.___gaugeOld__.valueLabel !== value) {
5619 state.___gaugeOld__.valueLabel = value;
5620 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5622 if(state.___gaugeOld__.minLabel !== min) {
5623 state.___gaugeOld__.minLabel = min;
5624 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5626 if(state.___gaugeOld__.maxLabel !== max) {
5627 state.___gaugeOld__.maxLabel = max;
5628 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5632 NETDATA.gaugeClearSelection = function(state) {
5633 if(typeof state.gaugeEvent !== 'undefined') {
5634 if(state.gaugeEvent.timer !== undefined) {
5635 clearTimeout(state.gaugeEvent.timer);
5638 state.gaugeEvent.timer = undefined;
5641 if(state.isAutoRefreshable() === true && state.data !== null) {
5642 NETDATA.gaugeChartUpdate(state, state.data);
5645 NETDATA.gaugeAnimation(state, false);
5646 NETDATA.gaugeSet(state, null, null, null);
5647 NETDATA.gaugeSetLabels(state, null, null, null);
5650 NETDATA.gaugeAnimation(state, true);
5654 NETDATA.gaugeSetSelection = function(state, t) {
5655 if(state.timeIsVisible(t) !== true)
5656 return NETDATA.gaugeClearSelection(state);
5658 var slot = state.calculateRowForTime(t);
5659 if(slot < 0 || slot >= state.data.result.length)
5660 return NETDATA.gaugeClearSelection(state);
5662 if(typeof state.gaugeEvent === 'undefined') {
5663 state.gaugeEvent = {
5671 var value = state.data.result[state.data.result.length - 1 - slot];
5672 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5673 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5675 // make sure it is zero based
5676 if(min > 0) min = 0;
5677 if(max < 0) max = 0;
5679 state.gaugeEvent.value = value;
5680 state.gaugeEvent.min = min;
5681 state.gaugeEvent.max = max;
5682 NETDATA.gaugeSetLabels(state, value, min, max);
5684 if(state.gaugeEvent.timer === undefined) {
5685 NETDATA.gaugeAnimation(state, false);
5687 state.gaugeEvent.timer = setTimeout(function() {
5688 state.gaugeEvent.timer = undefined;
5689 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5690 }, NETDATA.options.current.charts_selection_animation_delay);
5696 NETDATA.gaugeChartUpdate = function(state, data) {
5697 var value, min, max;
5699 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5703 NETDATA.gaugeSetLabels(state, null, null, null);
5706 value = data.result[0];
5707 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5708 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5709 if(value < min) min = value;
5710 if(value > max) max = value;
5712 // make sure it is zero based
5713 if(min > 0) min = 0;
5714 if(max < 0) max = 0;
5716 NETDATA.gaugeSetLabels(state, value, min, max);
5719 NETDATA.gaugeSet(state, value, min, max);
5723 NETDATA.gaugeChartCreate = function(state, data) {
5724 var self = $(state.element);
5725 // var chart = $(state.element_chart);
5727 var value = data.result[0];
5728 var min = self.data('gauge-min-value') || null;
5729 var max = self.data('gauge-max-value') || null;
5730 var adjust = self.data('gauge-adjust') || null;
5731 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5732 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5733 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5734 var stopColor = self.data('gauge-stop-color') || void 0;
5735 var generateGradient = self.data('gauge-generate-gradient') || false;
5738 min = NETDATA.commonMin.get(state);
5739 state.gaugeMin = null;
5742 state.gaugeMin = min;
5745 max = NETDATA.commonMax.get(state);
5746 state.gaugeMax = null;
5749 state.gaugeMax = max;
5751 // make sure it is zero based
5752 if(min > 0) min = 0;
5753 if(max < 0) max = 0;
5755 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5757 // case 'width': width = height * ratio; break;
5759 // default: height = width / ratio; break;
5761 //state.element.style.width = width.toString() + 'px';
5762 //state.element.style.height = height.toString() + 'px';
5767 lines: 12, // The number of lines to draw
5768 angle: 0.15, // The span of the gauge arc
5769 lineWidth: 0.50, // The line thickness
5770 radiusScale: 0.85, // Relative radius
5772 length: 0.8, // 0.9 The radius of the inner circle
5773 strokeWidth: 0.035, // The rotation offset
5774 color: pointerColor // Fill color
5776 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5777 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5778 colorStart: startColor, // Colors
5779 colorStop: stopColor, // just experiment with them
5780 strokeColor: strokeColor, // to see which ones work best for you
5781 generateGradient: (generateGradient === true),
5783 highDpiSupport: true // High resolution support
5786 if (generateGradient.constructor === Array) {
5788 // data-gauge-generate-gradient="[0, 50, 100]"
5789 // data-gauge-gradient-percent-color-0="#FFFFFF"
5790 // data-gauge-gradient-percent-color-50="#999900"
5791 // data-gauge-gradient-percent-color-100="#000000"
5793 options.percentColors = [];
5794 var len = generateGradient.length;
5796 var pcent = generateGradient[len];
5797 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5798 if(color !== false) {
5802 options.percentColors.unshift(a);
5805 if(options.percentColors.length === 0)
5806 delete options.percentColors;
5808 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5809 //noinspection PointlessArithmeticExpressionJS
5810 options.percentColors = [
5811 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5812 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5813 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5814 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5815 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5816 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5817 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5818 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5819 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5820 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5821 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5824 state.gauge_canvas = document.createElement('canvas');
5825 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5826 state.gauge_canvas.className = 'gaugeChart';
5827 state.gauge_canvas.width = width;
5828 state.gauge_canvas.height = height;
5829 state.element_chart.appendChild(state.gauge_canvas);
5831 var valuefontsize = Math.floor(height / 6);
5832 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5833 state.gaugeChartLabel = document.createElement('span');
5834 state.gaugeChartLabel.className = 'gaugeChartLabel';
5835 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5836 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5837 state.element_chart.appendChild(state.gaugeChartLabel);
5839 var titlefontsize = Math.round(valuefontsize / 2);
5841 state.gaugeChartTitle = document.createElement('span');
5842 state.gaugeChartTitle.className = 'gaugeChartTitle';
5843 state.gaugeChartTitle.innerText = state.title;
5844 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5845 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5846 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5847 state.element_chart.appendChild(state.gaugeChartTitle);
5849 var unitfontsize = Math.round(titlefontsize * 0.9);
5850 state.gaugeChartUnits = document.createElement('span');
5851 state.gaugeChartUnits.className = 'gaugeChartUnits';
5852 state.gaugeChartUnits.innerText = state.units;
5853 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5854 state.element_chart.appendChild(state.gaugeChartUnits);
5856 state.gaugeChartMin = document.createElement('span');
5857 state.gaugeChartMin.className = 'gaugeChartMin';
5858 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5859 state.element_chart.appendChild(state.gaugeChartMin);
5861 state.gaugeChartMax = document.createElement('span');
5862 state.gaugeChartMax.className = 'gaugeChartMax';
5863 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5864 state.element_chart.appendChild(state.gaugeChartMax);
5866 // when we just re-create the chart
5867 // do not animate the first update
5869 if(typeof state.gauge_instance !== 'undefined')
5872 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5874 state.___gaugeOld__ = {
5883 // we will always feed a percentage
5884 state.gauge_instance.minValue = 0;
5885 state.gauge_instance.maxValue = 100;
5887 NETDATA.gaugeAnimation(state, animate);
5888 NETDATA.gaugeSet(state, value, min, max);
5889 NETDATA.gaugeSetLabels(state, value, min, max);
5890 NETDATA.gaugeAnimation(state, true);
5894 // ----------------------------------------------------------------------------------------------------------------
5895 // Charts Libraries Registration
5897 NETDATA.chartLibraries = {
5899 initialize: NETDATA.dygraphInitialize,
5900 create: NETDATA.dygraphChartCreate,
5901 update: NETDATA.dygraphChartUpdate,
5902 resize: function(state) {
5903 if(typeof state.dygraph_instance.resize === 'function')
5904 state.dygraph_instance.resize();
5906 setSelection: NETDATA.dygraphSetSelection,
5907 clearSelection: NETDATA.dygraphClearSelection,
5908 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5911 format: function(state) { void(state); return 'json'; },
5912 options: function(state) { void(state); return 'ms|flip'; },
5913 legend: function(state) {
5914 return (this.isSparkline(state) === false)?'right-side':null;
5916 autoresize: function(state) { void(state); return true; },
5917 max_updates_to_recreate: function(state) { void(state); return 5000; },
5918 track_colors: function(state) { void(state); return true; },
5919 pixels_per_point: function(state) {
5920 return (this.isSparkline(state) === false)?3:2;
5922 isSparkline: function(state) {
5923 if(typeof state.dygraph_sparkline === 'undefined') {
5924 var t = $(state.element).data('dygraph-theme');
5925 state.dygraph_sparkline = (t === 'sparkline');
5927 return state.dygraph_sparkline;
5931 initialize: NETDATA.sparklineInitialize,
5932 create: NETDATA.sparklineChartCreate,
5933 update: NETDATA.sparklineChartUpdate,
5935 setSelection: undefined, // function(state, t) { void(state); return true; },
5936 clearSelection: undefined, // function(state) { void(state); return true; },
5937 toolboxPanAndZoom: null,
5940 format: function(state) { void(state); return 'array'; },
5941 options: function(state) { void(state); return 'flip|abs'; },
5942 legend: function(state) { void(state); return null; },
5943 autoresize: function(state) { void(state); return false; },
5944 max_updates_to_recreate: function(state) { void(state); return 5000; },
5945 track_colors: function(state) { void(state); return false; },
5946 pixels_per_point: function(state) { void(state); return 3; }
5949 initialize: NETDATA.peityInitialize,
5950 create: NETDATA.peityChartCreate,
5951 update: NETDATA.peityChartUpdate,
5953 setSelection: undefined, // function(state, t) { void(state); return true; },
5954 clearSelection: undefined, // function(state) { void(state); return true; },
5955 toolboxPanAndZoom: null,
5958 format: function(state) { void(state); return 'ssvcomma'; },
5959 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5960 legend: function(state) { void(state); return null; },
5961 autoresize: function(state) { void(state); return false; },
5962 max_updates_to_recreate: function(state) { void(state); return 5000; },
5963 track_colors: function(state) { void(state); return false; },
5964 pixels_per_point: function(state) { void(state); return 3; }
5967 initialize: NETDATA.morrisInitialize,
5968 create: NETDATA.morrisChartCreate,
5969 update: NETDATA.morrisChartUpdate,
5971 setSelection: undefined, // function(state, t) { void(state); return true; },
5972 clearSelection: undefined, // function(state) { void(state); return true; },
5973 toolboxPanAndZoom: null,
5976 format: function(state) { void(state); return 'json'; },
5977 options: function(state) { void(state); return 'objectrows|ms'; },
5978 legend: function(state) { void(state); return null; },
5979 autoresize: function(state) { void(state); return false; },
5980 max_updates_to_recreate: function(state) { void(state); return 50; },
5981 track_colors: function(state) { void(state); return false; },
5982 pixels_per_point: function(state) { void(state); return 15; }
5985 initialize: NETDATA.googleInitialize,
5986 create: NETDATA.googleChartCreate,
5987 update: NETDATA.googleChartUpdate,
5989 setSelection: undefined, //function(state, t) { void(state); return true; },
5990 clearSelection: undefined, //function(state) { void(state); return true; },
5991 toolboxPanAndZoom: null,
5994 format: function(state) { void(state); return 'datatable'; },
5995 options: function(state) { void(state); return ''; },
5996 legend: function(state) { void(state); return null; },
5997 autoresize: function(state) { void(state); return false; },
5998 max_updates_to_recreate: function(state) { void(state); return 300; },
5999 track_colors: function(state) { void(state); return false; },
6000 pixels_per_point: function(state) { void(state); return 4; }
6003 initialize: NETDATA.raphaelInitialize,
6004 create: NETDATA.raphaelChartCreate,
6005 update: NETDATA.raphaelChartUpdate,
6007 setSelection: undefined, // function(state, t) { void(state); return true; },
6008 clearSelection: undefined, // function(state) { void(state); return true; },
6009 toolboxPanAndZoom: null,
6012 format: function(state) { void(state); return 'json'; },
6013 options: function(state) { void(state); return ''; },
6014 legend: function(state) { void(state); return null; },
6015 autoresize: function(state) { void(state); return false; },
6016 max_updates_to_recreate: function(state) { void(state); return 5000; },
6017 track_colors: function(state) { void(state); return false; },
6018 pixels_per_point: function(state) { void(state); return 3; }
6021 initialize: NETDATA.c3Initialize,
6022 create: NETDATA.c3ChartCreate,
6023 update: NETDATA.c3ChartUpdate,
6025 setSelection: undefined, // function(state, t) { void(state); return true; },
6026 clearSelection: undefined, // function(state) { void(state); return true; },
6027 toolboxPanAndZoom: null,
6030 format: function(state) { void(state); return 'csvjsonarray'; },
6031 options: function(state) { void(state); return 'milliseconds'; },
6032 legend: function(state) { void(state); return null; },
6033 autoresize: function(state) { void(state); return false; },
6034 max_updates_to_recreate: function(state) { void(state); return 5000; },
6035 track_colors: function(state) { void(state); return false; },
6036 pixels_per_point: function(state) { void(state); return 15; }
6039 initialize: NETDATA.d3Initialize,
6040 create: NETDATA.d3ChartCreate,
6041 update: NETDATA.d3ChartUpdate,
6043 setSelection: undefined, // function(state, t) { void(state); return true; },
6044 clearSelection: undefined, // function(state) { void(state); return true; },
6045 toolboxPanAndZoom: null,
6048 format: function(state) { void(state); return 'json'; },
6049 options: function(state) { void(state); return ''; },
6050 legend: function(state) { void(state); return null; },
6051 autoresize: function(state) { void(state); return false; },
6052 max_updates_to_recreate: function(state) { void(state); return 5000; },
6053 track_colors: function(state) { void(state); return false; },
6054 pixels_per_point: function(state) { void(state); return 3; }
6057 initialize: NETDATA.easypiechartInitialize,
6058 create: NETDATA.easypiechartChartCreate,
6059 update: NETDATA.easypiechartChartUpdate,
6061 setSelection: NETDATA.easypiechartSetSelection,
6062 clearSelection: NETDATA.easypiechartClearSelection,
6063 toolboxPanAndZoom: null,
6066 format: function(state) { void(state); return 'array'; },
6067 options: function(state) { void(state); return 'absolute'; },
6068 legend: function(state) { void(state); return null; },
6069 autoresize: function(state) { void(state); return false; },
6070 max_updates_to_recreate: function(state) { void(state); return 5000; },
6071 track_colors: function(state) { void(state); return true; },
6072 pixels_per_point: function(state) { void(state); return 3; },
6076 initialize: NETDATA.gaugeInitialize,
6077 create: NETDATA.gaugeChartCreate,
6078 update: NETDATA.gaugeChartUpdate,
6080 setSelection: NETDATA.gaugeSetSelection,
6081 clearSelection: NETDATA.gaugeClearSelection,
6082 toolboxPanAndZoom: null,
6085 format: function(state) { void(state); return 'array'; },
6086 options: function(state) { void(state); return 'absolute'; },
6087 legend: function(state) { void(state); return null; },
6088 autoresize: function(state) { void(state); return false; },
6089 max_updates_to_recreate: function(state) { void(state); return 5000; },
6090 track_colors: function(state) { void(state); return true; },
6091 pixels_per_point: function(state) { void(state); return 3; },
6096 NETDATA.registerChartLibrary = function(library, url) {
6097 if(NETDATA.options.debug.libraries === true)
6098 console.log("registering chart library: " + library);
6100 NETDATA.chartLibraries[library].url = url;
6101 NETDATA.chartLibraries[library].initialized = true;
6102 NETDATA.chartLibraries[library].enabled = true;
6105 // ----------------------------------------------------------------------------------------------------------------
6106 // Load required JS libraries and CSS
6108 NETDATA.requiredJs = [
6110 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6112 isAlreadyLoaded: function() {
6113 // check if bootstrap is loaded
6114 if(typeof $().emulateTransitionEnd === 'function')
6117 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6122 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6123 isAlreadyLoaded: function() { return false; }
6127 NETDATA.requiredCSS = [
6129 url: NETDATA.themes.current.bootstrap_css,
6130 isAlreadyLoaded: function() {
6131 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6135 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6136 isAlreadyLoaded: function() { return false; }
6139 url: NETDATA.themes.current.dashboard_css,
6140 isAlreadyLoaded: function() { return false; }
6144 NETDATA.loadedRequiredJs = 0;
6145 NETDATA.loadRequiredJs = function(index, callback) {
6146 if(index >= NETDATA.requiredJs.length) {
6147 if(typeof callback === 'function')
6152 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6153 NETDATA.loadedRequiredJs++;
6154 NETDATA.loadRequiredJs(++index, callback);
6158 if(NETDATA.options.debug.main_loop === true)
6159 console.log('loading ' + NETDATA.requiredJs[index].url);
6162 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6166 url: NETDATA.requiredJs[index].url,
6169 xhrFields: { withCredentials: true } // required for the cookie
6172 if(NETDATA.options.debug.main_loop === true)
6173 console.log('loaded ' + NETDATA.requiredJs[index].url);
6176 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6178 .always(function() {
6179 NETDATA.loadedRequiredJs++;
6182 NETDATA.loadRequiredJs(++index, callback);
6186 NETDATA.loadRequiredJs(++index, callback);
6189 NETDATA.loadRequiredCSS = function(index) {
6190 if(index >= NETDATA.requiredCSS.length)
6193 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6194 NETDATA.loadRequiredCSS(++index);
6198 if(NETDATA.options.debug.main_loop === true)
6199 console.log('loading ' + NETDATA.requiredCSS[index].url);
6201 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6202 NETDATA.loadRequiredCSS(++index);
6206 // ----------------------------------------------------------------------------------------------------------------
6207 // Registry of netdata hosts
6210 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6211 chart_div_offset: 100, // give that space above the chart when scrolling to it
6212 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6213 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6215 ms_penalty: 0, // the time penalty of the next alarm
6216 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6217 // if alarms are shown faster than: one per 500ms
6219 notifications: false, // when true, the browser supports notifications (may not be granted though)
6220 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6221 first_notification_id: 0, // the id of the first alarm_log entry for this session
6222 // this is used to prevent CLEAR notifications for past events
6223 // notifications_shown: [],
6225 server: null, // the server to connect to for fetching alarms
6226 current: null, // the list of raised alarms - updated in the background
6227 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6229 notify: function(entry) {
6230 // console.log('alarm ' + entry.unique_id);
6232 if(entry.updated === true) {
6233 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6237 var value_string = entry.value_string;
6239 if(NETDATA.alarms.current !== null) {
6240 // get the current value_string
6241 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6242 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6243 value_string = t.value_string;
6246 var name = entry.name.replace(/_/g, ' ');
6247 var status = entry.status.toLowerCase();
6248 var title = name + ' = ' + value_string.toString();
6249 var tag = entry.alarm_id;
6250 var icon = 'images/seo-performance-128.png';
6251 var interaction = false;
6255 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6257 switch(entry.status) {
6265 case 'UNINITIALIZED':
6269 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6270 // console.log('alarm ' + entry.unique_id + ' is not current');
6273 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6274 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6277 if(entry.no_clear_notification === true) {
6278 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6281 title = name + ' back to normal (' + value_string.toString() + ')';
6282 icon = 'images/check-mark-2-128-green.png';
6283 interaction = false;
6287 if(entry.old_status === 'CRITICAL')
6288 status = 'demoted to ' + entry.status.toLowerCase();
6290 icon = 'images/alert-128-orange.png';
6291 interaction = false;
6295 if(entry.old_status === 'WARNING')
6296 status = 'escalated to ' + entry.status.toLowerCase();
6298 icon = 'images/alert-128-red.png';
6303 console.log('invalid alarm status ' + entry.status);
6308 // cleanup old notifications with the same alarm_id as this one
6309 // FIXME: it does not seem to work on any web browser!
6310 var len = NETDATA.alarms.notifications_shown.length;
6312 var n = NETDATA.alarms.notifications_shown[len];
6313 if(n.data.alarm_id === entry.alarm_id) {
6314 console.log('removing old alarm ' + n.data.unique_id);
6316 // close the notification
6319 // remove it from the array
6320 NETDATA.alarms.notifications_shown.splice(len, 1);
6321 len = NETDATA.alarms.notifications_shown.length;
6328 setTimeout(function() {
6329 // show this notification
6330 // console.log('new notification: ' + title);
6331 var n = new Notification(title, {
6332 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6334 requireInteraction: interaction,
6335 icon: NETDATA.serverDefault + icon,
6339 n.onclick = function(event) {
6340 event.preventDefault();
6341 NETDATA.alarms.onclick(event.target.data);
6345 // NETDATA.alarms.notifications_shown.push(n);
6346 // console.log(entry);
6347 }, NETDATA.alarms.ms_penalty);
6349 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6353 scrollToChart: function(chart_id) {
6354 if(typeof chart_id === 'string') {
6355 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6356 if(typeof offset !== 'undefined') {
6357 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6364 scrollToAlarm: function(alarm) {
6365 if(typeof alarm === 'object') {
6366 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6368 if(ret === true && NETDATA.options.page_is_visible === false)
6370 // 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.');
6375 notifyAll: function() {
6376 // console.log('FETCHING ALARM LOG');
6377 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6378 // console.log('ALARM LOG FETCHED');
6380 if(data === null || typeof data !== 'object') {
6381 console.log('invalid alarms log response');
6385 if(data.length === 0) {
6386 console.log('received empty alarm log');
6390 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6392 data.sort(function(a, b) {
6393 if(a.unique_id > b.unique_id) return -1;
6394 if(a.unique_id < b.unique_id) return 1;
6398 NETDATA.alarms.ms_penalty = 0;
6400 var len = data.length;
6402 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6403 NETDATA.alarms.notify(data[len]);
6406 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6409 NETDATA.alarms.last_notification_id = data[0].unique_id;
6410 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6411 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6415 check_notifications: function() {
6416 // returns true if we should fire 1+ notifications
6418 if(NETDATA.alarms.notifications !== true) {
6419 // console.log('notifications not available');
6423 if(Notification.permission !== 'granted') {
6424 // console.log('notifications not granted');
6428 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6429 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6431 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6432 // console.log('new alarms detected');
6435 //else console.log('no new alarms');
6437 // else console.log('cannot process alarms');
6442 get: function(what, callback) {
6444 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6448 'Cache-Control': 'no-cache, no-store',
6449 'Pragma': 'no-cache'
6451 xhrFields: { withCredentials: true } // required for the cookie
6453 .done(function(data) {
6454 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6455 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6457 if(typeof callback === 'function')
6458 return callback(data);
6461 NETDATA.error(415, NETDATA.alarms.server);
6463 if(typeof callback === 'function')
6464 return callback(null);
6468 update_forever: function() {
6469 NETDATA.alarms.get('active', function(data) {
6471 NETDATA.alarms.current = data;
6473 if(NETDATA.alarms.check_notifications() === true) {
6474 NETDATA.alarms.notifyAll();
6477 if (typeof NETDATA.alarms.callback === 'function') {
6478 NETDATA.alarms.callback(data);
6481 // Health monitoring is disabled on this netdata
6482 if(data.status === false) return;
6485 setTimeout(NETDATA.alarms.update_forever, 10000);
6489 get_log: function(last_id, callback) {
6490 // console.log('fetching all log after ' + last_id.toString());
6492 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6496 'Cache-Control': 'no-cache, no-store',
6497 'Pragma': 'no-cache'
6499 xhrFields: { withCredentials: true } // required for the cookie
6501 .done(function(data) {
6502 if(typeof callback === 'function')
6503 return callback(data);
6506 NETDATA.error(416, NETDATA.alarms.server);
6508 if(typeof callback === 'function')
6509 return callback(null);
6514 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6516 NETDATA.alarms.last_notification_id =
6517 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6519 if(NETDATA.alarms.onclick === null)
6520 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6522 if(netdataShowAlarms === true) {
6523 NETDATA.alarms.update_forever();
6525 if('Notification' in window) {
6526 // console.log('notifications available');
6527 NETDATA.alarms.notifications = true;
6529 if(Notification.permission === 'default')
6530 Notification.requestPermission();
6536 // ----------------------------------------------------------------------------------------------------------------
6537 // Registry of netdata hosts
6539 NETDATA.registry = {
6540 server: null, // the netdata registry server
6541 person_guid: null, // the unique ID of this browser / user
6542 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6543 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6544 machines: null, // the user's other URLs
6545 machines_array: null, // the user's other URLs in an array
6548 parsePersonUrls: function(person_urls) {
6549 // console.log(person_urls);
6550 NETDATA.registry.person_urls = person_urls;
6553 NETDATA.registry.machines = {};
6554 NETDATA.registry.machines_array = [];
6556 var apu = person_urls;
6559 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6560 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6566 accesses: apu[i][3],
6570 obj.alternate_urls.push(apu[i][1]);
6572 NETDATA.registry.machines[apu[i][0]] = obj;
6573 NETDATA.registry.machines_array.push(obj);
6576 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6578 var pu = NETDATA.registry.machines[apu[i][0]];
6579 if(pu.last_t < apu[i][2]) {
6581 pu.last_t = apu[i][2];
6582 pu.name = apu[i][4];
6584 pu.accesses += apu[i][3];
6585 pu.alternate_urls.push(apu[i][1]);
6590 if(typeof netdataRegistryCallback === 'function')
6591 netdataRegistryCallback(NETDATA.registry.machines_array);
6595 if(netdataRegistry !== true) return;
6597 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6599 NETDATA.registry.server = data.registry;
6600 NETDATA.registry.machine_guid = data.machine_guid;
6601 NETDATA.registry.hostname = data.hostname;
6603 NETDATA.registry.access(2, function (person_urls) {
6604 NETDATA.registry.parsePersonUrls(person_urls);
6611 hello: function(host, callback) {
6612 host = NETDATA.fixHost(host);
6614 // send HELLO to a netdata server:
6615 // 1. verifies the server is reachable
6616 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6618 url: host + '/api/v1/registry?action=hello',
6622 'Cache-Control': 'no-cache, no-store',
6623 'Pragma': 'no-cache'
6625 xhrFields: { withCredentials: true } // required for the cookie
6627 .done(function(data) {
6628 if(typeof data.status !== 'string' || data.status !== 'ok') {
6629 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6633 if(typeof callback === 'function')
6634 return callback(data);
6637 NETDATA.error(407, host);
6639 if(typeof callback === 'function')
6640 return callback(null);
6644 access: function(max_redirects, callback) {
6645 // send ACCESS to a netdata registry:
6646 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6647 // 2. it responds with a list of netdata servers we know
6648 // the registry identifies us using a cookie it sets the first time we access it
6649 // the registry may respond with a redirect URL to send us to another registry
6651 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),
6655 'Cache-Control': 'no-cache, no-store',
6656 'Pragma': 'no-cache'
6658 xhrFields: { withCredentials: true } // required for the cookie
6660 .done(function(data) {
6661 var redirect = null;
6662 if(typeof data.registry === 'string')
6663 redirect = data.registry;
6665 if(typeof data.status !== 'string' || data.status !== 'ok') {
6666 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6671 if(redirect !== null && max_redirects > 0) {
6672 NETDATA.registry.server = redirect;
6673 NETDATA.registry.access(max_redirects - 1, callback);
6676 if(typeof callback === 'function')
6677 return callback(null);
6681 if(typeof data.person_guid === 'string')
6682 NETDATA.registry.person_guid = data.person_guid;
6684 if(typeof callback === 'function')
6685 return callback(data.urls);
6689 NETDATA.error(410, NETDATA.registry.server);
6691 if(typeof callback === 'function')
6692 return callback(null);
6696 delete: function(delete_url, callback) {
6697 // send DELETE to a netdata registry:
6699 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),
6703 'Cache-Control': 'no-cache, no-store',
6704 'Pragma': 'no-cache'
6706 xhrFields: { withCredentials: true } // required for the cookie
6708 .done(function(data) {
6709 if(typeof data.status !== 'string' || data.status !== 'ok') {
6710 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6714 if(typeof callback === 'function')
6715 return callback(data);
6718 NETDATA.error(412, NETDATA.registry.server);
6720 if(typeof callback === 'function')
6721 return callback(null);
6725 search: function(machine_guid, callback) {
6726 // SEARCH for the URLs of a machine:
6728 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,
6732 'Cache-Control': 'no-cache, no-store',
6733 'Pragma': 'no-cache'
6735 xhrFields: { withCredentials: true } // required for the cookie
6737 .done(function(data) {
6738 if(typeof data.status !== 'string' || data.status !== 'ok') {
6739 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6743 if(typeof callback === 'function')
6744 return callback(data);
6747 NETDATA.error(418, NETDATA.registry.server);
6749 if(typeof callback === 'function')
6750 return callback(null);
6754 switch: function(new_person_guid, callback) {
6757 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,
6761 'Cache-Control': 'no-cache, no-store',
6762 'Pragma': 'no-cache'
6764 xhrFields: { withCredentials: true } // required for the cookie
6766 .done(function(data) {
6767 if(typeof data.status !== 'string' || data.status !== 'ok') {
6768 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6772 if(typeof callback === 'function')
6773 return callback(data);
6776 NETDATA.error(414, NETDATA.registry.server);
6778 if(typeof callback === 'function')
6779 return callback(null);
6784 // ----------------------------------------------------------------------------------------------------------------
6787 if(typeof netdataPrepCallback === 'function')
6788 netdataPrepCallback();
6790 NETDATA.errorReset();
6791 NETDATA.loadRequiredCSS(0);
6793 NETDATA._loadjQuery(function() {
6794 NETDATA.loadRequiredJs(0, function() {
6795 if(typeof $().emulateTransitionEnd !== 'function') {
6796 // bootstrap is not available
6797 NETDATA.options.current.show_help = false;
6800 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6801 if(NETDATA.options.debug.main_loop === true)
6802 console.log('starting chart refresh thread');
6808 })(window, document);