1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
4 /*global netdataNoDygraphs *//* boolean, disable dygraph charts
6 /*global netdataNoSparklines *//* boolean, disable sparkline charts
8 /*global netdataNoPeitys *//* boolean, disable peity charts
10 /*global netdataNoGoogleCharts *//* boolean, disable google charts
12 /*global netdataNoMorris *//* boolean, disable morris charts
14 /*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
16 /*global netdataNoGauge *//* boolean, disable gauge.js charts
18 /*global netdataNoD3 *//* boolean, disable d3 charts
20 /*global netdataNoC3 *//* boolean, disable c3 charts
22 /*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
24 /*global netdataDontStart *//* boolean, do not start the thread to process the charts
26 /*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
28 /*global netdataRegistry:true *//* boolean, use the netdata registry
30 /*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
31 * (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
34 /*global netdataShowHelp:true *//* boolean, disable charts help
36 /*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
40 /*global netdataCallback *//* function, callback to be called when netdata is ready to start
42 * netdata will be running while this is called
43 * (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
46 /*global netdataServer *//* string, the URL of the netdata server to use
47 * (default: the URL the page is hosted at) */
49 // ----------------------------------------------------------------------------
52 var NETDATA = window.NETDATA || {};
54 (function(window, document) {
55 // ------------------------------------------------------------------------
56 // compatibility fixes
58 // fix IE issue with console
59 if(!window.console) { window.console = { log: function(){} }; }
61 // if string.endsWith is not defined, define it
62 if(typeof String.prototype.endsWith !== 'function') {
63 String.prototype.endsWith = function(s) {
64 if(s.length > this.length) return false;
65 return this.slice(-s.length) === s;
69 // if string.startsWith is not defined, define it
70 if(typeof String.prototype.startsWith !== 'function') {
71 String.prototype.startsWith = function(s) {
72 if(s.length > this.length) return false;
73 return this.slice(s.length) === s;
77 NETDATA.name2id = function(s) {
86 // ----------------------------------------------------------------------------------------------------------------
87 // Detect the netdata server
89 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91 NETDATA._scriptSource = function() {
94 if(typeof document.currentScript !== 'undefined') {
95 script = document.currentScript;
98 var all_scripts = document.getElementsByTagName('script');
99 script = all_scripts[all_scripts.length - 1];
102 if (typeof script.getAttribute.length !== 'undefined')
105 script = script.getAttribute('src', -1);
110 if(typeof netdataServer !== 'undefined')
111 NETDATA.serverDefault = netdataServer;
113 var s = NETDATA._scriptSource();
114 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
116 console.log('WARNING: Cannot detect the URL of the netdata server.');
117 NETDATA.serverDefault = null;
121 if(NETDATA.serverDefault === null)
122 NETDATA.serverDefault = '';
123 else if(NETDATA.serverDefault.slice(-1) !== '/')
124 NETDATA.serverDefault += '/';
126 // default URLs for all the external files we need
127 // make them RELATIVE so that the whole thing can also be
128 // installed under a web server
129 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-1.3.1.min.js';
134 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
135 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
136 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
137 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
138 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
139 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
140 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
141 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
142 NETDATA.google_js = 'https://www.google.com/jsapi';
146 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
147 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
148 background: '#FFFFFF',
149 foreground: '#000000',
152 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
153 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
154 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
155 '#329262', '#3B3EAC' ],
156 easypiechart_track: '#f0f0f0',
157 easypiechart_scale: '#dfe0e0',
158 gauge_pointer: '#C0C0C0',
159 gauge_stroke: '#F0F0F0',
160 gauge_gradient: false
163 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
164 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
165 background: '#272b30',
166 foreground: '#C8C8C8',
169 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
170 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
171 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
172 '#a6a479', '#a66da8' ],
174 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
175 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
176 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
177 '#329262', '#3B3EFF' ],
178 easypiechart_track: '#373b40',
179 easypiechart_scale: '#373b40',
180 gauge_pointer: '#474b50',
181 gauge_stroke: '#373b40',
182 gauge_gradient: false
186 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
187 NETDATA.themes.current = NETDATA.themes[netdataTheme];
189 NETDATA.themes.current = NETDATA.themes.white;
191 NETDATA.colors = NETDATA.themes.current.colors;
193 // these are the colors Google Charts are using
194 // we have them here to attempt emulate their look and feel on the other chart libraries
195 // http://there4.io/2012/05/02/google-chart-color-list/
196 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
197 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
198 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
200 // an alternative set
201 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
202 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
203 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
205 if(typeof netdataShowHelp === 'undefined')
206 netdataShowHelp = true;
208 if(typeof netdataShowAlarms === 'undefined')
209 netdataShowAlarms = false;
211 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
212 netdataRegistryAfterMs = 1500;
214 if(typeof netdataRegistry === 'undefined') {
215 // backward compatibility
216 netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
218 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
219 netdataRegistry = true;
222 // ----------------------------------------------------------------------------------------------------------------
223 // detect if this is probably a slow device
225 var isSlowDeviceResult = undefined;
226 var isSlowDevice = function() {
227 if(isSlowDeviceResult !== undefined)
228 return isSlowDeviceResult;
231 var ua = navigator.userAgent.toLowerCase();
233 var iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
234 var android = /android/.test(ua) && !window.MSStream;
235 isSlowDeviceResult = (iOS === true || android === true);
238 isSlowDeviceResult = false;
241 return isSlowDeviceResult;
244 // ----------------------------------------------------------------------------------------------------------------
245 // the defaults for all charts
247 // if the user does not specify any of these, the following will be used
249 NETDATA.chartDefaults = {
250 host: NETDATA.serverDefault, // the server to get data from
251 width: '100%', // the chart width - can be null
252 height: '100%', // the chart height - can be null
253 min_width: null, // the chart minimum width - can be null
254 library: 'dygraph', // the graphing library to use
255 method: 'average', // the grouping method
256 before: 0, // panning
257 after: -600, // panning
258 pixels_per_point: 1, // the detail of the chart
259 fill_luminance: 0.8 // luminance of colors in solid areas
262 // ----------------------------------------------------------------------------------------------------------------
266 pauseCallback: null, // a callback when we are really paused
268 pause: false, // when enabled we don't auto-refresh the charts
270 targets: null, // an array of all the state objects that are
271 // currently active (independently of their
272 // viewport visibility)
274 updated_dom: true, // when true, the DOM has been updated with
275 // new elements we have to check.
277 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
278 // rendering charts continuously.
279 // used with .current.fast_render_timeframe
281 page_is_visible: true, // when true, this page is visible
283 auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
284 // auto-refresher for some time (when a chart is
285 // performing pan or zoom, we need to stop refreshing
286 // all other charts, to have the maximum speed for
287 // rendering the chart that is panned or zoomed).
288 // Used with .current.global_pan_sync_time
290 last_resized: Date.now(), // the timestamp of the last resize request
292 last_page_scroll: 0, // the timestamp the last time the page was scrolled
294 // the current profile
295 // we may have many...
297 pixels_per_point: isSlowDevice()?5:1, // the minimum pixels per point for all charts
298 // increase this to speed javascript up
299 // each chart library has its own limit too
300 // the max of this and the chart library is used
301 // the final is calculated every time, so a change
302 // here will have immediate effect on the next chart
305 idle_between_charts: 100, // ms - how much time to wait between chart updates
307 fast_render_timeframe: 200, // ms - render continuously until this time of continuous
308 // rendering has been reached
309 // this setting is used to make it render e.g. 10
310 // charts at once, sleep idle_between_charts time
311 // and continue for another 10 charts.
313 idle_between_loops: 500, // ms - if all charts have been updated, wait this
314 // time before starting again.
316 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
318 idle_lost_focus: 500, // ms - when the window does not have focus, check
319 // if focus has been regained, every this time
321 global_pan_sync_time: 1000, // ms - when you pan or zoom a chart, the background
322 // auto-refreshing of charts is paused for this amount
325 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
326 // of time before setting up synchronized selections
329 sync_selection: true, // enable or disable selection sync
331 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
333 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
335 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
337 update_only_visible: true, // enable or disable visibility management
339 parallel_refresher: (isSlowDevice() === false), // enable parallel refresh of charts
341 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
343 destroy_on_hide: (isSlowDevice() === true), // destroy charts when they are not visible
345 show_help: netdataShowHelp, // when enabled the charts will show some help
346 show_help_delay_show_ms: 500,
347 show_help_delay_hide_ms: 0,
349 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
351 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
352 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
354 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
356 smooth_plot: (isSlowDevice() === false), // enable smooth plot, where possible
358 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
360 color_fill_opacity_line: 1.0,
361 color_fill_opacity_area: 0.2,
362 color_fill_opacity_stacked: 0.8,
364 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
365 pan_and_zoom_factor_multiplier_control: 2.0,
366 pan_and_zoom_factor_multiplier_shift: 3.0,
367 pan_and_zoom_factor_multiplier_alt: 4.0,
369 abort_ajax_on_scroll: false, // kill pending ajax page scroll
370 async_on_scroll: false, // sync/async onscroll handler
371 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
373 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
375 setOptionCallback: function() { }
383 chart_data_url: false,
384 chart_errors: false, // FIXME: remember to set it to false before merging
392 NETDATA.statistics = {
395 refreshes_active_max: 0
399 // ----------------------------------------------------------------------------------------------------------------
400 // local storage options
402 NETDATA.localStorage = {
405 callback: {} // only used for resetting back to defaults
408 NETDATA.localStorageTested = -1;
409 NETDATA.localStorageTest = function() {
410 if(NETDATA.localStorageTested !== -1)
411 return NETDATA.localStorageTested;
413 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
416 localStorage.setItem(test, test);
417 localStorage.removeItem(test);
418 NETDATA.localStorageTested = true;
421 NETDATA.localStorageTested = false;
425 NETDATA.localStorageTested = false;
427 return NETDATA.localStorageTested;
430 NETDATA.localStorageGet = function(key, def, callback) {
433 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
434 NETDATA.localStorage.default[key.toString()] = def;
435 NETDATA.localStorage.callback[key.toString()] = callback;
438 if(NETDATA.localStorageTest() === true) {
440 // console.log('localStorage: loading "' + key.toString() + '"');
441 ret = localStorage.getItem(key.toString());
442 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
443 if(ret === null || ret === 'undefined') {
444 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
445 localStorage.setItem(key.toString(), JSON.stringify(def));
449 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
450 ret = JSON.parse(ret);
451 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
455 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
460 if(typeof ret === 'undefined' || ret === 'undefined') {
461 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
465 NETDATA.localStorage.current[key.toString()] = ret;
469 NETDATA.localStorageSet = function(key, value, callback) {
470 if(typeof value === 'undefined' || value === 'undefined') {
471 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
474 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
475 NETDATA.localStorage.default[key.toString()] = value;
476 NETDATA.localStorage.current[key.toString()] = value;
477 NETDATA.localStorage.callback[key.toString()] = callback;
480 if(NETDATA.localStorageTest() === true) {
481 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
483 localStorage.setItem(key.toString(), JSON.stringify(value));
486 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
490 NETDATA.localStorage.current[key.toString()] = value;
494 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
495 var keys = Object.keys(obj);
496 var len = keys.length;
500 if(typeof obj[i] === 'object') {
501 //console.log('object ' + prefix + '.' + i.toString());
502 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
506 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
510 NETDATA.setOption = function(key, value) {
511 if(key.toString() === 'setOptionCallback') {
512 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
513 NETDATA.options.current[key.toString()] = value;
514 NETDATA.options.current.setOptionCallback();
517 else if(NETDATA.options.current[key.toString()] !== value) {
518 var name = 'options.' + key.toString();
520 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
521 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
523 //console.log(NETDATA.localStorage);
524 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
525 //console.log(NETDATA.options);
526 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
528 if(typeof NETDATA.options.current.setOptionCallback === 'function')
529 NETDATA.options.current.setOptionCallback();
535 NETDATA.getOption = function(key) {
536 return NETDATA.options.current[key.toString()];
539 // read settings from local storage
540 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
542 // always start with this option enabled.
543 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
545 NETDATA.resetOptions = function() {
546 var keys = Object.keys(NETDATA.localStorage.default);
547 var len = keys.length;
550 var a = i.split('.');
552 if(a[0] === 'options') {
553 if(a[1] === 'setOptionCallback') continue;
554 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
555 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
557 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
559 else if(a[0] === 'chart_heights') {
560 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
561 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
567 // ----------------------------------------------------------------------------------------------------------------
569 if(NETDATA.options.debug.main_loop === true)
570 console.log('welcome to NETDATA');
572 NETDATA.onresizeCallback = null;
573 NETDATA.onresize = function() {
574 NETDATA.options.last_resized = Date.now();
577 if(typeof NETDATA.onresizeCallback === 'function')
578 NETDATA.onresizeCallback();
581 NETDATA.onscroll_updater_count = 0;
582 NETDATA.onscroll_updater_running = false;
583 NETDATA.onscroll_updater_last_run = 0;
584 NETDATA.onscroll_updater_watchdog = null;
585 NETDATA.onscroll_updater_max_duration = 0;
586 NETDATA.onscroll_updater_above_threshold_count = 0;
587 NETDATA.onscroll_updater = function() {
588 NETDATA.onscroll_updater_running = true;
589 NETDATA.onscroll_updater_count++;
590 var start = Date.now();
592 var targets = NETDATA.options.targets;
593 var len = targets.length;
595 // when the user scrolls he sees that we have
596 // hidden all the not-visible charts
597 // using this little function we try to switch
598 // the charts back to visible quickly
601 if(NETDATA.options.abort_ajax_on_scroll === true) {
602 // we have to cancel pending requests too
605 if (targets[len]._updating === true) {
606 if (typeof targets[len].xhr !== 'undefined') {
607 targets[len].xhr.abort();
608 targets[len].running = false;
609 targets[len]._updating = false;
611 targets[len].isVisible();
616 // just find which chart is visible
619 targets[len].isVisible();
622 var end = Date.now();
623 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
625 if(NETDATA.options.current.async_on_scroll === false) {
626 var dt = end - start;
627 if(dt > NETDATA.onscroll_updater_max_duration) {
628 // console.log('max onscroll event handler duration increased to ' + dt);
629 NETDATA.onscroll_updater_max_duration = dt;
632 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
633 // console.log('slow: ' + dt);
634 NETDATA.onscroll_updater_above_threshold_count++;
636 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
637 NETDATA.setOption('async_on_scroll', true);
638 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
643 NETDATA.onscroll_updater_last_run = start;
644 NETDATA.onscroll_updater_running = false;
647 NETDATA.onscroll = function() {
648 // console.log('onscroll');
650 NETDATA.options.last_page_scroll = Date.now();
651 NETDATA.options.auto_refresher_stop_until = 0;
653 if(NETDATA.options.targets === null) return;
655 if(NETDATA.options.current.async_on_scroll === true) {
657 if(NETDATA.onscroll_updater_running === false) {
658 NETDATA.onscroll_updater_running = true;
659 setTimeout(NETDATA.onscroll_updater, 0);
662 if(NETDATA.onscroll_updater_watchdog !== null)
663 clearTimeout(NETDATA.onscroll_updater_watchdog);
665 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
666 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
667 // console.log('watchdog');
668 NETDATA.onscroll_updater();
671 NETDATA.onscroll_updater_watchdog = null;
677 NETDATA.onscroll_updater();
681 window.onresize = NETDATA.onresize;
682 window.onscroll = NETDATA.onscroll;
684 // ----------------------------------------------------------------------------------------------------------------
687 NETDATA.errorCodes = {
688 100: { message: "Cannot load chart library", alert: true },
689 101: { message: "Cannot load jQuery", alert: true },
690 402: { message: "Chart library not found", alert: false },
691 403: { message: "Chart library not enabled/is failed", alert: false },
692 404: { message: "Chart not found", alert: false },
693 405: { message: "Cannot download charts index from server", alert: true },
694 406: { message: "Invalid charts index downloaded from server", alert: true },
695 407: { message: "Cannot HELLO netdata server", alert: false },
696 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
697 409: { message: "Cannot ACCESS netdata registry", alert: false },
698 410: { message: "Netdata registry ACCESS failed", alert: false },
699 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
700 412: { message: "Netdata registry DELETE failed", alert: false },
701 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
702 414: { message: "Netdata registry SWITCH failed", alert: false },
703 415: { message: "Netdata alarms download failed", alert: false },
704 416: { message: "Netdata alarms log download failed", alert: false },
705 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
706 418: { message: "Netdata registry SEARCH failed", alert: false }
708 NETDATA.errorLast = {
714 NETDATA.error = function(code, msg) {
715 NETDATA.errorLast.code = code;
716 NETDATA.errorLast.message = msg;
717 NETDATA.errorLast.datetime = Date.now();
719 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
722 if(typeof netdataErrorCallback === 'function') {
723 ret = netdataErrorCallback('system', code, msg);
726 if(ret && NETDATA.errorCodes[code].alert)
727 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
730 NETDATA.errorReset = function() {
731 NETDATA.errorLast.code = 0;
732 NETDATA.errorLast.message = "You are doing fine!";
733 NETDATA.errorLast.datetime = 0;
736 // ----------------------------------------------------------------------------------------------------------------
737 // commonMin & commonMax
739 NETDATA.commonMin = {
743 get: function(state) {
744 if(typeof state.__commonMin === 'undefined') {
745 // get the commonMin setting
746 var self = $(state.element);
747 state.__commonMin = self.data('common-min') || null;
750 var min = state.data.min;
751 var name = state.__commonMin;
754 // we don't need commonMin
755 //state.log('no need for commonMin');
759 var t = this.keys[name];
760 if(typeof t === 'undefined') {
762 this.keys[name] = {};
766 var uuid = state.uuid;
767 if(typeof t[uuid] !== 'undefined') {
768 if(t[uuid] === min) {
769 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
770 return this.latest[name];
772 else if(min < this.latest[name]) {
773 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
775 this.latest[name] = min;
783 // find the common min
786 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
788 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
789 this.latest[name] = m;
794 NETDATA.commonMax = {
798 get: function(state) {
799 if(typeof state.__commonMax === 'undefined') {
800 // get the commonMax setting
801 var self = $(state.element);
802 state.__commonMax = self.data('common-max') || null;
805 var max = state.data.max;
806 var name = state.__commonMax;
809 // we don't need commonMax
810 //state.log('no need for commonMax');
814 var t = this.keys[name];
815 if(typeof t === 'undefined') {
817 this.keys[name] = {};
821 var uuid = state.uuid;
822 if(typeof t[uuid] !== 'undefined') {
823 if(t[uuid] === max) {
824 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
825 return this.latest[name];
827 else if(max > this.latest[name]) {
828 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
830 this.latest[name] = max;
838 // find the common max
841 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
843 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
844 this.latest[name] = m;
849 // ----------------------------------------------------------------------------------------------------------------
852 // When multiple charts need the same chart, we avoid downloading it
853 // multiple times (and having it in browser memory multiple time)
854 // by using this registry.
856 // Every time we download a chart definition, we save it here with .add()
857 // Then we try to get it back with .get(). If that fails, we download it.
859 NETDATA.fixHost = function(host) {
860 while(host.slice(-1) === '/')
861 host = host.substring(0, host.length - 1);
866 NETDATA.chartRegistry = {
869 fixid: function(id) {
870 return id.replace(/:/g, "_").replace(/\//g, "_");
873 add: function(host, id, data) {
874 host = this.fixid(host);
877 if(typeof this.charts[host] === 'undefined')
878 this.charts[host] = {};
880 //console.log('added ' + host + '/' + id);
881 this.charts[host][id] = data;
884 get: function(host, id) {
885 host = this.fixid(host);
888 if(typeof this.charts[host] === 'undefined')
891 if(typeof this.charts[host][id] === 'undefined')
894 //console.log('cached ' + host + '/' + id);
895 return this.charts[host][id];
898 downloadAll: function(host, callback) {
899 host = NETDATA.fixHost(host);
904 url: host + '/api/v1/charts',
907 xhrFields: { withCredentials: true } // required for the cookie
909 .done(function(data) {
911 var h = NETDATA.chartRegistry.fixid(host);
912 self.charts[h] = data.charts;
914 else NETDATA.error(406, host + '/api/v1/charts');
916 if(typeof callback === 'function')
917 return callback(data);
920 NETDATA.error(405, host + '/api/v1/charts');
922 if(typeof callback === 'function')
923 return callback(null);
928 // ----------------------------------------------------------------------------------------------------------------
929 // Global Pan and Zoom on charts
931 // Using this structure are synchronize all the charts, so that
932 // when you pan or zoom one, all others are automatically refreshed
933 // to the same timespan.
935 NETDATA.globalPanAndZoom = {
936 seq: 0, // timestamp ms
937 // every time a chart is panned or zoomed
938 // we set the timestamp here
939 // then we use it as a sequence number
940 // to find if other charts are synchronized
941 // to this time-range
943 master: null, // the master chart (state), to which all others
946 force_before_ms: null, // the timespan to sync all other charts
947 force_after_ms: null,
952 setMaster: function(state, after, before) {
953 if(NETDATA.options.current.sync_pan_and_zoom === false)
956 if(this.master !== null && this.master !== state)
957 this.master.resetChart(true, true);
959 var now = Date.now();
962 this.force_after_ms = after;
963 this.force_before_ms = before;
964 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
966 if(typeof this.callback === 'function')
967 this.callback(true, after, before);
971 clearMaster: function() {
972 if(this.master !== null) {
973 var st = this.master;
980 this.force_after_ms = null;
981 this.force_before_ms = null;
982 NETDATA.options.auto_refresher_stop_until = 0;
984 if(typeof this.callback === 'function')
985 this.callback(false, 0, 0);
988 // is the given state the master of the global
989 // pan and zoom sync?
990 isMaster: function(state) {
991 return (this.master === state);
994 // are we currently have a global pan and zoom sync?
995 isActive: function() {
996 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
999 // check if a chart, other than the master
1000 // needs to be refreshed, due to the global pan and zoom
1001 shouldBeAutoRefreshed: function(state) {
1002 if(this.master === null || this.seq === 0)
1005 //if(state.needsRecreation())
1008 return (state.tm.pan_and_zoom_seq !== this.seq);
1012 // ----------------------------------------------------------------------------------------------------------------
1013 // dimensions selection
1016 // move color assignment to dimensions, here
1018 var dimensionStatus = function(parent, label, name_div, value_div, color) {
1019 this.enabled = false;
1020 this.parent = parent;
1022 this.name_div = null;
1023 this.value_div = null;
1024 this.color = NETDATA.themes.current.foreground;
1025 this.selected = (parent.unselected_count === 0);
1027 this.setOptions(name_div, value_div, color);
1030 dimensionStatus.prototype.invalidate = function() {
1031 this.name_div = null;
1032 this.value_div = null;
1033 this.enabled = false;
1036 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1039 if(this.name_div !== name_div) {
1040 this.name_div = name_div;
1041 this.name_div.title = this.label;
1042 this.name_div.style.color = this.color;
1043 if(this.selected === false)
1044 this.name_div.className = 'netdata-legend-name not-selected';
1046 this.name_div.className = 'netdata-legend-name selected';
1049 if(this.value_div !== value_div) {
1050 this.value_div = value_div;
1051 this.value_div.title = this.label;
1052 this.value_div.style.color = this.color;
1053 if(this.selected === false)
1054 this.value_div.className = 'netdata-legend-value not-selected';
1056 this.value_div.className = 'netdata-legend-value selected';
1059 this.enabled = true;
1063 dimensionStatus.prototype.setHandler = function() {
1064 if(this.enabled === false) return;
1068 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1069 this.name_div.onclick = this.value_div.onclick = function(e) {
1071 if(ds.isSelected()) {
1073 if(e.shiftKey === true || e.ctrlKey === true) {
1074 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1077 if(ds.parent.countSelected() === 0)
1078 ds.parent.selectAll();
1081 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1082 if(ds.parent.countSelected() === 1) {
1083 ds.parent.selectAll();
1086 ds.parent.selectNone();
1092 // this is not selected
1093 if(e.shiftKey === true || e.ctrlKey === true) {
1094 // control or shift key is pressed -> select this too
1098 // no key is pressed -> select only this
1099 ds.parent.selectNone();
1104 ds.parent.state.redrawChart();
1108 dimensionStatus.prototype.select = function() {
1109 if(this.enabled === false) return;
1111 this.name_div.className = 'netdata-legend-name selected';
1112 this.value_div.className = 'netdata-legend-value selected';
1113 this.selected = true;
1116 dimensionStatus.prototype.unselect = function() {
1117 if(this.enabled === false) return;
1119 this.name_div.className = 'netdata-legend-name not-selected';
1120 this.value_div.className = 'netdata-legend-value hidden';
1121 this.selected = false;
1124 dimensionStatus.prototype.isSelected = function() {
1125 return(this.enabled === true && this.selected === true);
1128 // ----------------------------------------------------------------------------------------------------------------
1130 var dimensionsVisibility = function(state) {
1133 this.dimensions = {};
1134 this.selected_count = 0;
1135 this.unselected_count = 0;
1138 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1139 if(typeof this.dimensions[label] === 'undefined') {
1141 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1144 this.dimensions[label].setOptions(name_div, value_div, color);
1146 return this.dimensions[label];
1149 dimensionsVisibility.prototype.dimensionGet = function(label) {
1150 return this.dimensions[label];
1153 dimensionsVisibility.prototype.invalidateAll = function() {
1154 var keys = Object.keys(this.dimensions);
1155 var len = keys.length;
1157 this.dimensions[keys[len]].invalidate();
1160 dimensionsVisibility.prototype.selectAll = function() {
1161 var keys = Object.keys(this.dimensions);
1162 var len = keys.length;
1164 this.dimensions[keys[len]].select();
1167 dimensionsVisibility.prototype.countSelected = function() {
1169 var keys = Object.keys(this.dimensions);
1170 var len = keys.length;
1172 if(this.dimensions[keys[len]].isSelected()) selected++;
1177 dimensionsVisibility.prototype.selectNone = function() {
1178 var keys = Object.keys(this.dimensions);
1179 var len = keys.length;
1181 this.dimensions[keys[len]].unselect();
1184 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1186 this.selected_count = 0;
1187 this.unselected_count = 0;
1189 var len = array.length;
1191 var ds = this.dimensions[array[len]];
1192 if(typeof ds === 'undefined') {
1193 // console.log(array[i] + ' is not found');
1196 else if(ds.isSelected()) {
1198 this.selected_count++;
1202 this.unselected_count++;
1206 if(this.selected_count === 0 && this.unselected_count !== 0) {
1208 return this.selected2BooleanArray(array);
1215 // ----------------------------------------------------------------------------------------------------------------
1216 // global selection sync
1218 NETDATA.globalSelectionSync = {
1220 dont_sync_before: 0,
1225 if(this.state !== null)
1226 this.state.globalSelectionSyncStop();
1230 if(this.state !== null) {
1231 this.state.globalSelectionSyncDelay();
1236 // ----------------------------------------------------------------------------------------------------------------
1237 // Our state object, where all per-chart values are stored
1239 var chartState = function(element) {
1240 var self = $(element);
1241 this.element = element;
1244 // all private functions should use 'that', instead of 'this'
1247 /* error() - private
1248 * show an error instead of the chart
1250 var error = function(msg) {
1253 if(typeof netdataErrorCallback === 'function') {
1254 ret = netdataErrorCallback('chart', that.id, msg);
1258 that.element.innerHTML = that.id + ': ' + msg;
1259 that.enabled = false;
1260 that.current = that.pan;
1264 // GUID - a unique identifier for the chart
1265 this.uuid = NETDATA.guid();
1267 // string - the name of chart
1268 this.id = self.data('netdata');
1270 // string - the key for localStorage settings
1271 this.settings_id = self.data('id') || null;
1273 // the user given dimensions of the element
1274 this.width = self.data('width') || NETDATA.chartDefaults.width;
1275 this.height = self.data('height') || NETDATA.chartDefaults.height;
1276 this.height_original = this.height;
1278 if(this.settings_id !== null) {
1279 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1280 // this is the callback that will be called
1281 // if and when the user resets all localStorage variables
1282 // to their defaults
1284 resizeChartToHeight(height);
1288 // string - the netdata server URL, without any path
1289 this.host = self.data('host') || NETDATA.chartDefaults.host;
1291 // make sure the host does not end with /
1292 // all netdata API requests use absolute paths
1293 while(this.host.slice(-1) === '/')
1294 this.host = this.host.substring(0, this.host.length - 1);
1296 // string - the grouping method requested by the user
1297 this.method = self.data('method') || NETDATA.chartDefaults.method;
1299 // the time-range requested by the user
1300 this.after = self.data('after') || NETDATA.chartDefaults.after;
1301 this.before = self.data('before') || NETDATA.chartDefaults.before;
1303 // the pixels per point requested by the user
1304 this.pixels_per_point = self.data('pixels-per-point') || 1;
1305 this.points = self.data('points') || null;
1307 // the dimensions requested by the user
1308 this.dimensions = self.data('dimensions') || null;
1310 // the chart library requested by the user
1311 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1313 // how many retries we have made to load chart data from the server
1314 this.retries_on_data_failures = 0;
1316 // object - the chart library used
1317 this.library = null;
1321 this.colors_assigned = {};
1322 this.colors_available = null;
1324 // the element already created by the user
1325 this.element_message = null;
1327 // the element with the chart
1328 this.element_chart = null;
1330 // the element with the legend of the chart (if created by us)
1331 this.element_legend = null;
1332 this.element_legend_childs = {
1337 perfect_scroller: null, // the container to apply perfect scroller to
1341 this.chart_url = null; // string - the url to download chart info
1342 this.chart = null; // object - the chart as downloaded from the server
1344 this.title = self.data('title') || null; // the title of the chart
1345 this.units = self.data('units') || null; // the units of the chart dimensions
1346 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1347 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1349 this.running = false; // boolean - true when the chart is being refreshed now
1350 this.enabled = true; // boolean - is the chart enabled for refresh?
1351 this.paused = false; // boolean - is the chart paused for any reason?
1352 this.selected = false; // boolean - is the chart shown a selection?
1353 this.debug = false; // boolean - console.log() debug info about this chart
1355 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1356 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1357 this.requested_after = null; // milliseconds - the timestamp of the request after param
1358 this.requested_before = null; // milliseconds - the timestamp of the request before param
1359 this.requested_padding = null;
1360 this.view_after = 0;
1361 this.view_before = 0;
1363 this.value_decimal_detail = -1;
1364 var d = self.data('decimal-digits');
1365 if(typeof d === 'number') {
1366 this.value_decimal_detail = 1;
1368 this.value_decimal_detail *= 10;
1374 force_update_at: 0, // the timestamp to force the update at
1375 force_before_ms: null,
1376 force_after_ms: null
1381 force_update_at: 0, // the timestamp to force the update at
1382 force_before_ms: null,
1383 force_after_ms: null
1388 force_update_at: 0, // the timestamp to force the update at
1389 force_before_ms: null,
1390 force_after_ms: null
1393 // this is a pointer to one of the sub-classes below
1395 this.current = this.auto;
1397 // check the requested library is available
1398 // we don't initialize it here - it will be initialized when
1399 // this chart will be first used
1400 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1401 NETDATA.error(402, that.library_name);
1402 error('chart library "' + that.library_name + '" is not found');
1405 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1406 NETDATA.error(403, that.library_name);
1407 error('chart library "' + that.library_name + '" is not enabled');
1411 that.library = NETDATA.chartLibraries[that.library_name];
1413 // milliseconds - the time the last refresh took
1414 this.refresh_dt_ms = 0;
1416 // if we need to report the rendering speed
1417 // find the element that needs to be updated
1418 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1420 if(refresh_dt_element_name !== null) {
1421 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1424 this.refresh_dt_element = null;
1426 this.dimensions_visibility = new dimensionsVisibility(this);
1428 this._updating = false;
1430 // ============================================================================================================
1431 // PRIVATE FUNCTIONS
1433 var createDOM = function() {
1434 if(that.enabled === false) return;
1436 if(that.element_message !== null) that.element_message.innerHTML = '';
1437 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1438 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1440 that.element.innerHTML = '';
1442 that.element_message = document.createElement('div');
1443 that.element_message.className = 'netdata-message icon hidden';
1444 that.element.appendChild(that.element_message);
1446 that.element_chart = document.createElement('div');
1447 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1448 that.element.appendChild(that.element_chart);
1450 if(that.hasLegend() === true) {
1451 that.element.className = "netdata-container-with-legend";
1452 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1454 that.element_legend = document.createElement('div');
1455 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1456 that.element.appendChild(that.element_legend);
1459 that.element.className = "netdata-container";
1460 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1462 that.element_legend = null;
1464 that.element_legend_childs.series = null;
1466 if(typeof(that.width) === 'string')
1467 $(that.element).css('width', that.width);
1468 else if(typeof(that.width) === 'number')
1469 $(that.element).css('width', that.width + 'px');
1471 if(typeof(that.library.aspect_ratio) === 'undefined') {
1472 if(typeof(that.height) === 'string')
1473 that.element.style.height = that.height;
1474 else if(typeof(that.height) === 'number')
1475 that.element.style.height = that.height.toString() + 'px';
1478 var w = that.element.offsetWidth;
1479 if(w === null || w === 0) {
1480 // the div is hidden
1481 // this will resize the chart when next viewed
1482 that.tm.last_resized = 0;
1485 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1488 if(NETDATA.chartDefaults.min_width !== null)
1489 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1491 that.tm.last_dom_created = Date.now();
1497 * initialize state variables
1498 * destroy all (possibly) created state elements
1499 * create the basic DOM for a chart
1501 var init = function() {
1502 if(that.enabled === false) return;
1504 that.paused = false;
1505 that.selected = false;
1507 that.chart_created = false; // boolean - is the library.create() been called?
1508 that.updates_counter = 0; // numeric - the number of refreshes made so far
1509 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1510 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1513 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1514 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1515 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1517 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1518 last_updated: 0, // the timestamp the chart last updated with data
1519 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1521 // Used with NETDATA.globalPanAndZoom.seq
1522 last_visible_check: 0, // the time we last checked if it is visible
1523 last_resized: 0, // the time the chart was resized
1524 last_hidden: 0, // the time the chart was hidden
1525 last_unhidden: 0, // the time the chart was unhidden
1526 last_autorefreshed: 0 // the time the chart was last refreshed
1529 that.data = null; // the last data as downloaded from the netdata server
1530 that.data_url = 'invalid://'; // string - the last url used to update the chart
1531 that.data_points = 0; // number - the number of points returned from netdata
1532 that.data_after = 0; // milliseconds - the first timestamp of the data
1533 that.data_before = 0; // milliseconds - the last timestamp of the data
1534 that.data_update_every = 0; // milliseconds - the frequency to update the data
1536 that.tm.last_initialized = Date.now();
1539 that.setMode('auto');
1542 var maxMessageFontSize = function() {
1543 var screenHeight = screen.height;
1544 var el = that.element;
1546 // normally we want a font size, as tall as the element
1547 var h = el.clientHeight;
1549 // but give it some air, 20% let's say, or 5 pixels min
1550 var lost = Math.max(h * 0.2, 5);
1553 // center the text, vertically
1554 var paddingTop = (lost - 5) / 2;
1556 // but check the width too
1557 // it should fit 10 characters in it
1558 var w = el.clientWidth / 10;
1560 paddingTop += (h - w) / 2;
1564 // and don't make it too huge
1565 // 5% of the screen size is good
1566 if(h > screenHeight / 20) {
1567 paddingTop += (h - (screenHeight / 20)) / 2;
1568 h = screenHeight / 20;
1572 that.element_message.style.fontSize = h.toString() + 'px';
1573 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1576 var showMessageIcon = function(icon) {
1577 that.element_message.innerHTML = icon;
1578 maxMessageFontSize();
1579 $(that.element_message).removeClass('hidden');
1580 that.___messageHidden___ = undefined;
1583 var hideMessage = function() {
1584 if(typeof that.___messageHidden___ === 'undefined') {
1585 that.___messageHidden___ = true;
1586 $(that.element_message).addClass('hidden');
1590 var showRendering = function() {
1592 if(that.chart !== null) {
1593 if(that.chart.chart_type === 'line')
1594 icon = '<i class="fa fa-line-chart"></i>';
1596 icon = '<i class="fa fa-area-chart"></i>';
1599 icon = '<i class="fa fa-area-chart"></i>';
1601 showMessageIcon(icon + ' netdata');
1604 var showLoading = function() {
1605 if(that.chart_created === false) {
1606 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1612 var isHidden = function() {
1613 return (typeof that.___chartIsHidden___ !== 'undefined');
1616 // hide the chart, when it is not visible - called from isVisible()
1617 var hideChart = function() {
1618 // hide it, if it is not already hidden
1619 if(isHidden() === true) return;
1621 if(that.chart_created === true) {
1622 if(NETDATA.options.current.destroy_on_hide === true) {
1623 // we should destroy it
1628 that.element_chart.style.display = 'none';
1629 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1630 that.tm.last_hidden = Date.now();
1633 // This works, but I not sure there are no corner cases somewhere
1634 // so it is commented - if the user has memory issues he can
1635 // set Destroy on Hide for all charts
1636 // that.data = null;
1640 that.___chartIsHidden___ = true;
1643 // unhide the chart, when it is visible - called from isVisible()
1644 var unhideChart = function() {
1645 if(isHidden() === false) return;
1647 that.___chartIsHidden___ = undefined;
1648 that.updates_since_last_unhide = 0;
1650 if(that.chart_created === false) {
1651 // we need to re-initialize it, to show our background
1652 // logo in bootstrap tabs, until the chart loads
1656 that.tm.last_unhidden = Date.now();
1657 that.element_chart.style.display = '';
1658 if(that.element_legend !== null) that.element_legend.style.display = '';
1664 var canBeRendered = function() {
1665 return (isHidden() === false && that.isVisible(true) === true);
1668 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1669 var callChartLibraryUpdateSafely = function(data) {
1672 if(canBeRendered() === false)
1675 if(NETDATA.options.debug.chart_errors === true)
1676 status = that.library.update(that, data);
1679 status = that.library.update(that, data);
1686 if(status === false) {
1687 error('chart failed to be updated as ' + that.library_name);
1694 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1695 var callChartLibraryCreateSafely = function(data) {
1698 if(canBeRendered() === false)
1701 if(NETDATA.options.debug.chart_errors === true)
1702 status = that.library.create(that, data);
1705 status = that.library.create(that, data);
1712 if(status === false) {
1713 error('chart failed to be created as ' + that.library_name);
1717 that.chart_created = true;
1718 that.updates_since_last_creation = 0;
1722 // ----------------------------------------------------------------------------------------------------------------
1725 // resizeChart() - private
1726 // to be called just before the chart library to make sure that
1727 // a properly sized dom is available
1728 var resizeChart = function() {
1729 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1730 if(that.chart_created === false) return;
1732 if(that.needsRecreation()) {
1735 else if(typeof that.library.resize === 'function') {
1736 that.library.resize(that);
1738 if(that.element_legend_childs.perfect_scroller !== null)
1739 Ps.update(that.element_legend_childs.perfect_scroller);
1741 maxMessageFontSize();
1744 that.tm.last_resized = Date.now();
1748 // this is the actual chart resize algorithm
1750 // - resize the entire container
1751 // - update the internal states
1752 // - resize the chart as the div changes height
1753 // - update the scrollbar of the legend
1754 var resizeChartToHeight = function(h) {
1756 that.element.style.height = h;
1758 if(that.settings_id !== null)
1759 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1761 var now = Date.now();
1762 NETDATA.options.last_page_scroll = now;
1763 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1766 that.tm.last_resized = 0;
1770 this.resizeHandler = function(e) {
1773 if(typeof this.event_resize === 'undefined'
1774 || this.event_resize.chart_original_w === 'undefined'
1775 || this.event_resize.chart_original_h === 'undefined')
1776 this.event_resize = {
1777 chart_original_w: this.element.clientWidth,
1778 chart_original_h: this.element.clientHeight,
1782 if(e.type === 'touchstart') {
1783 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1784 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1787 this.event_resize.mouse_start_x = e.clientX;
1788 this.event_resize.mouse_start_y = e.clientY;
1791 this.event_resize.chart_start_w = this.element.clientWidth;
1792 this.event_resize.chart_start_h = this.element.clientHeight;
1793 this.event_resize.chart_last_w = this.element.clientWidth;
1794 this.event_resize.chart_last_h = this.element.clientHeight;
1796 var now = Date.now();
1797 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1798 // double click / double tap event
1800 // console.dir(this.element_legend_childs.content);
1801 // console.dir(this.element_legend_childs.perfect_scroller);
1803 // the optimal height of the chart
1804 // showing the entire legend
1805 var optimal = this.event_resize.chart_last_h
1806 + this.element_legend_childs.perfect_scroller.scrollHeight
1807 - this.element_legend_childs.perfect_scroller.clientHeight;
1809 // if we are not optimal, be optimal
1810 if(this.event_resize.chart_last_h !== optimal) {
1811 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1812 resizeChartToHeight(optimal.toString() + 'px');
1815 // else if the current height is not the original/saved height
1816 // reset to the original/saved height
1817 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1818 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1819 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1822 // else if the current height is not the internal default height
1823 // reset to the internal default height
1824 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1825 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1826 resizeChartToHeight(this.height_original.toString());
1829 // else if the current height is not the firstchild's clientheight
1831 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1832 var parent_rect = this.element.getBoundingClientRect();
1833 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1834 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1836 // console.log(parent_rect);
1837 // console.log(content_rect);
1838 // console.log(wanted);
1840 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1841 if(this.event_resize.chart_last_h !== wanted)
1842 resizeChartToHeight(wanted.toString() + 'px');
1846 this.event_resize.last = now;
1848 // process movement event
1849 document.onmousemove =
1850 document.ontouchmove =
1851 this.element_legend_childs.resize_handler.onmousemove =
1852 this.element_legend_childs.resize_handler.ontouchmove =
1857 case 'mousemove': y = e.clientY; break;
1858 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1862 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1864 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1865 resizeChartToHeight(newH.toString() + 'px');
1866 that.event_resize.chart_last_h = newH;
1871 // process end event
1872 document.onmouseup =
1873 document.ontouchend =
1874 this.element_legend_childs.resize_handler.onmouseup =
1875 this.element_legend_childs.resize_handler.ontouchend =
1879 // remove all the hooks
1880 document.onmouseup =
1881 document.onmousemove =
1882 document.ontouchmove =
1883 document.ontouchend =
1884 that.element_legend_childs.resize_handler.onmousemove =
1885 that.element_legend_childs.resize_handler.ontouchmove =
1886 that.element_legend_childs.resize_handler.onmouseout =
1887 that.element_legend_childs.resize_handler.onmouseup =
1888 that.element_legend_childs.resize_handler.ontouchend =
1891 // allow auto-refreshes
1892 NETDATA.options.auto_refresher_stop_until = 0;
1898 var noDataToShow = function() {
1899 showMessageIcon('<i class="fa fa-warning"></i> empty');
1900 that.legendUpdateDOM();
1901 that.tm.last_autorefreshed = Date.now();
1902 // that.data_update_every = 30 * 1000;
1903 //that.element_chart.style.display = 'none';
1904 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1905 //that.___chartIsHidden___ = true;
1908 // ============================================================================================================
1911 this.error = function(msg) {
1915 this.setMode = function(m) {
1916 if(this.current !== null && this.current.name === m) return;
1919 this.current = this.auto;
1920 else if(m === 'pan')
1921 this.current = this.pan;
1922 else if(m === 'zoom')
1923 this.current = this.zoom;
1925 this.current = this.auto;
1927 this.current.force_update_at = 0;
1928 this.current.force_before_ms = null;
1929 this.current.force_after_ms = null;
1931 this.tm.last_mode_switch = Date.now();
1934 // ----------------------------------------------------------------------------------------------------------------
1935 // global selection sync
1937 // prevent to global selection sync for some time
1938 this.globalSelectionSyncDelay = function(ms) {
1939 if(NETDATA.options.current.sync_selection === false)
1942 if(typeof ms === 'number')
1943 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1945 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1948 // can we globally apply selection sync?
1949 this.globalSelectionSyncAbility = function() {
1950 if(NETDATA.options.current.sync_selection === false)
1953 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1956 this.globalSelectionSyncIsMaster = function() {
1957 return (NETDATA.globalSelectionSync.state === this);
1960 // this chart is the master of the global selection sync
1961 this.globalSelectionSyncBeMaster = function() {
1963 if(this.globalSelectionSyncIsMaster()) {
1964 if(this.debug === true)
1965 this.log('sync: I am the master already.');
1970 if(NETDATA.globalSelectionSync.state) {
1971 if(this.debug === true)
1972 this.log('sync: I am not the sync master. Resetting global sync.');
1974 this.globalSelectionSyncStop();
1977 // become the master
1978 if(this.debug === true)
1979 this.log('sync: becoming sync master.');
1981 this.selected = true;
1982 NETDATA.globalSelectionSync.state = this;
1984 // find the all slaves
1985 var targets = NETDATA.options.targets;
1986 var len = targets.length;
1988 var st = targets[len];
1991 if(this.debug === true)
1992 st.log('sync: not adding me to sync');
1994 else if(st.globalSelectionSyncIsEligible()) {
1995 if(this.debug === true)
1996 st.log('sync: adding to sync as slave');
1998 st.globalSelectionSyncBeSlave();
2002 // this.globalSelectionSyncDelay(100);
2005 // can the chart participate to the global selection sync as a slave?
2006 this.globalSelectionSyncIsEligible = function() {
2007 return (this.enabled === true
2008 && this.library !== null
2009 && typeof this.library.setSelection === 'function'
2010 && this.isVisible() === true
2011 && this.chart_created === true);
2014 // this chart becomes a slave of the global selection sync
2015 this.globalSelectionSyncBeSlave = function() {
2016 if(NETDATA.globalSelectionSync.state !== this)
2017 NETDATA.globalSelectionSync.slaves.push(this);
2020 // sync all the visible charts to the given time
2021 // this is to be called from the chart libraries
2022 this.globalSelectionSync = function(t) {
2023 if(this.globalSelectionSyncAbility() === false)
2026 if(this.globalSelectionSyncIsMaster() === false) {
2027 if(this.debug === true)
2028 this.log('sync: trying to be sync master.');
2030 this.globalSelectionSyncBeMaster();
2032 if(this.globalSelectionSyncAbility() === false)
2036 NETDATA.globalSelectionSync.last_t = t;
2037 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2042 // stop syncing all charts to the given time
2043 this.globalSelectionSyncStop = function() {
2044 if(NETDATA.globalSelectionSync.slaves.length) {
2045 if(this.debug === true)
2046 this.log('sync: cleaning up...');
2048 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2050 if(that.debug === true)
2051 st.log('sync: not adding me to sync stop');
2054 if(that.debug === true)
2055 st.log('sync: removed slave from sync');
2057 st.clearSelection();
2061 NETDATA.globalSelectionSync.last_t = 0;
2062 NETDATA.globalSelectionSync.slaves = [];
2063 NETDATA.globalSelectionSync.state = null;
2066 this.clearSelection();
2069 this.setSelection = function(t) {
2070 if(typeof this.library.setSelection === 'function')
2071 this.selected = (this.library.setSelection(this, t) === true);
2073 this.selected = true;
2075 if(this.selected === true && this.debug === true)
2076 this.log('selection set to ' + t.toString());
2078 return this.selected;
2081 this.clearSelection = function() {
2082 if(this.selected === true) {
2083 if(typeof this.library.clearSelection === 'function')
2084 this.selected = (this.library.clearSelection(this) !== true);
2086 this.selected = false;
2088 if(this.selected === false && this.debug === true)
2089 this.log('selection cleared');
2094 return this.selected;
2097 // find if a timestamp (ms) is shown in the current chart
2098 this.timeIsVisible = function(t) {
2099 return (t >= this.data_after && t <= this.data_before);
2102 this.calculateRowForTime = function(t) {
2103 if(this.timeIsVisible(t) === false) return -1;
2104 return Math.floor((t - this.data_after) / this.data_update_every);
2107 // ----------------------------------------------------------------------------------------------------------------
2110 this.log = function(msg) {
2111 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2114 this.pauseChart = function() {
2115 if(this.paused === false) {
2116 if(this.debug === true)
2117 this.log('pauseChart()');
2123 this.unpauseChart = function() {
2124 if(this.paused === true) {
2125 if(this.debug === true)
2126 this.log('unpauseChart()');
2128 this.paused = false;
2132 this.resetChart = function(dont_clear_master, dont_update) {
2133 if(this.debug === true)
2134 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2136 if(typeof dont_clear_master === 'undefined')
2137 dont_clear_master = false;
2139 if(typeof dont_update === 'undefined')
2140 dont_update = false;
2142 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2143 if(this.debug === true)
2144 this.log('resetChart() diverting to clearMaster().');
2145 // this will call us back with master === true
2146 NETDATA.globalPanAndZoom.clearMaster();
2150 this.clearSelection();
2152 this.tm.pan_and_zoom_seq = 0;
2154 this.setMode('auto');
2155 this.current.force_update_at = 0;
2156 this.current.force_before_ms = null;
2157 this.current.force_after_ms = null;
2158 this.tm.last_autorefreshed = 0;
2159 this.paused = false;
2160 this.selected = false;
2161 this.enabled = true;
2162 // this.debug = false;
2164 // do not update the chart here
2165 // or the chart will flip-flop when it is the master
2166 // of a selection sync and another chart becomes
2169 if(dont_update !== true && this.isVisible() === true) {
2174 this.updateChartPanOrZoom = function(after, before) {
2175 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2178 if(this.debug === true)
2181 if(before < after) {
2182 if(this.debug === true)
2183 this.log(logme + 'flipped parameters, rejecting it.');
2188 if(typeof this.fixed_min_duration === 'undefined')
2189 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2191 var min_duration = this.fixed_min_duration;
2192 var current_duration = Math.round(this.view_before - this.view_after);
2194 // round the numbers
2195 after = Math.round(after);
2196 before = Math.round(before);
2198 // align them to update_every
2199 // stretching them further away
2200 after -= after % this.data_update_every;
2201 before += this.data_update_every - (before % this.data_update_every);
2203 // the final wanted duration
2204 var wanted_duration = before - after;
2206 // to allow panning, accept just a point below our minimum
2207 if((current_duration - this.data_update_every) < min_duration)
2208 min_duration = current_duration - this.data_update_every;
2210 // we do it, but we adjust to minimum size and return false
2211 // when the wanted size is below the current and the minimum
2213 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2214 if(this.debug === true)
2215 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2217 min_duration = this.fixed_min_duration;
2219 var dt = (min_duration - wanted_duration) / 2;
2222 wanted_duration = before - after;
2226 var tolerance = this.data_update_every * 2;
2227 var movement = Math.abs(before - this.view_before);
2229 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2230 if(this.debug === true)
2231 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2235 if(this.current.name === 'auto') {
2236 this.log(logme + 'caller called me with mode: ' + this.current.name);
2237 this.setMode('pan');
2240 if(this.debug === true)
2241 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2243 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2244 this.current.force_after_ms = after;
2245 this.current.force_before_ms = before;
2246 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2250 this.legendFormatValueChartDecimals = -1;
2251 this.legendFormatValueDecimalsFromMinMax = function(min, max) {
2255 delta = Math.abs(min);
2257 delta = Math.abs(max - min);
2259 if(delta > 1000) this.legendFormatValueChartDecimals = 0;
2260 else if(delta > 10 ) this.legendFormatValueChartDecimals = 1;
2261 else if(delta > 1 ) this.legendFormatValueChartDecimals = 2;
2262 else if(delta > 0.1 ) this.legendFormatValueChartDecimals = 3;
2263 else this.legendFormatValueChartDecimals = 4;
2266 this.legendFormatValue = function(value) {
2267 if(typeof value !== 'number') return '-';
2271 if(this.value_decimal_detail !== -1) {
2272 dmin = dmax = this.value_decimal_detail;
2275 if(this.legendFormatValueChartDecimals < 0) {
2278 if(abs > 1000) dmax = 0;
2279 else if(abs > 10 ) dmax = 1;
2280 else if(abs > 1) dmax = 2;
2281 else if(abs > 0.1) dmax = 3;
2285 dmin = dmax = this.legendFormatValueChartDecimals;
2288 return value.toLocaleString(undefined, {
2289 // style: 'decimal',
2290 // minimumIntegerDigits: 1,
2291 // minimumSignificantDigits: 1,
2292 // maximumSignificantDigits: 1,
2294 minimumFractionDigits: dmin,
2295 maximumFractionDigits: dmax
2299 this.legendSetLabelValue = function(label, value) {
2300 var series = this.element_legend_childs.series[label];
2301 if(typeof series === 'undefined') return;
2302 if(series.value === null && series.user === null) return;
2305 // this slows down firefox and edge significantly
2306 // since it requires to use innerHTML(), instead of innerText()
2308 // if the value has not changed, skip DOM update
2309 //if(series.last === value) return;
2312 if(typeof value === 'number') {
2313 var v = Math.abs(value);
2314 s = r = this.legendFormatValue(value);
2316 if(typeof series.last === 'number') {
2317 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2318 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2319 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2321 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2331 series.last = value;
2335 var s = this.legendFormatValue(value);
2337 // caching: do not update the update to show the same value again
2338 if(s === series.last_shown_value) return;
2339 series.last_shown_value = s;
2341 if(series.value !== null) series.value.innerText = s;
2342 if(series.user !== null) series.user.innerText = s;
2345 this.__legendSetDateString = function(date) {
2346 if(date !== this.__last_shown_legend_date) {
2347 this.element_legend_childs.title_date.innerText = date;
2348 this.__last_shown_legend_date = date;
2352 this.__legendSetTimeString = function(time) {
2353 if(time !== this.__last_shown_legend_time) {
2354 this.element_legend_childs.title_time.innerText = time;
2355 this.__last_shown_legend_time = time;
2359 this.__legendSetUnitsString = function(units) {
2360 if(units !== this.__last_shown_legend_units) {
2361 this.element_legend_childs.title_units.innerText = units;
2362 this.__last_shown_legend_units = units;
2366 this.legendSetDateLast = {
2372 this.legendSetDate = function(ms) {
2373 if(typeof ms !== 'number') {
2374 this.legendShowUndefined();
2378 if(this.legendSetDateLast.ms !== ms) {
2379 var d = new Date(ms);
2380 this.legendSetDateLast.ms = ms;
2381 this.legendSetDateLast.date = d.toLocaleDateString();
2382 this.legendSetDateLast.time = d.toLocaleTimeString();
2385 if(this.element_legend_childs.title_date !== null)
2386 this.__legendSetDateString(this.legendSetDateLast.date);
2388 if(this.element_legend_childs.title_time !== null)
2389 this.__legendSetTimeString(this.legendSetDateLast.time);
2391 if(this.element_legend_childs.title_units !== null)
2392 this.__legendSetUnitsString(this.units)
2395 this.legendShowUndefined = function() {
2396 if(this.element_legend_childs.title_date !== null)
2397 this.__legendSetDateString(' ');
2399 if(this.element_legend_childs.title_time !== null)
2400 this.__legendSetTimeString(this.chart.name);
2402 if(this.element_legend_childs.title_units !== null)
2403 this.__legendSetUnitsString(' ');
2405 if(this.data && this.element_legend_childs.series !== null) {
2406 var labels = this.data.dimension_names;
2407 var i = labels.length;
2409 var label = labels[i];
2411 if(typeof label === 'undefined' || typeof this.element_legend_childs.series[label] === 'undefined') continue;
2412 this.legendSetLabelValue(label, null);
2417 this.legendShowLatestValues = function() {
2418 if(this.chart === null) return;
2419 if(this.selected) return;
2421 if(this.data === null || this.element_legend_childs.series === null) {
2422 this.legendShowUndefined();
2426 var show_undefined = true;
2427 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2428 show_undefined = false;
2430 if(show_undefined) {
2431 this.legendShowUndefined();
2435 this.legendSetDate(this.view_before);
2437 var labels = this.data.dimension_names;
2438 var i = labels.length;
2440 var label = labels[i];
2442 if(typeof label === 'undefined') continue;
2443 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2446 this.legendSetLabelValue(label, null);
2448 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2452 this.legendReset = function() {
2453 this.legendShowLatestValues();
2456 // this should be called just ONCE per dimension per chart
2457 this._chartDimensionColor = function(label) {
2458 if(this.colors === null) this.chartColors();
2460 if(typeof this.colors_assigned[label] === 'undefined') {
2461 if(this.colors_available.length === 0) {
2462 var len = NETDATA.themes.current.colors.length;
2464 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2467 this.colors_assigned[label] = this.colors_available.shift();
2469 if(this.debug === true)
2470 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2473 if(this.debug === true)
2474 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2477 this.colors.push(this.colors_assigned[label]);
2478 return this.colors_assigned[label];
2481 this.chartColors = function() {
2482 if(this.colors !== null) return this.colors;
2485 this.colors_available = [];
2487 // add the standard colors
2488 var len = NETDATA.themes.current.colors.length;
2490 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2492 // add the user supplied colors
2493 var c = $(this.element).data('colors');
2494 // this.log('read colors: ' + c);
2495 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2496 if(typeof c !== 'string') {
2497 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2507 this.colors_available.unshift(c[len]);
2508 // this.log('adding color: ' + c[len]);
2517 this.legendUpdateDOM = function() {
2518 var needed = false, dim, keys, len, i;
2520 // check that the legend DOM is up to date for the downloaded dimensions
2521 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2522 // this.log('the legend does not have any series - requesting legend update');
2525 else if(this.data === null) {
2526 // this.log('the chart does not have any data - requesting legend update');
2529 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2533 var labels = this.data.dimension_names.toString();
2534 if(labels !== this.element_legend_childs.series.labels_key) {
2537 if(this.debug === true)
2538 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2542 if(needed === false) {
2543 // make sure colors available
2546 // do we have to update the current values?
2547 // we do this, only when the visible chart is current
2548 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2549 if(this.debug === true)
2550 this.log('chart is in latest position... updating values on legend...');
2552 //var labels = this.data.dimension_names;
2553 //var i = labels.length;
2555 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2559 if(this.colors === null) {
2560 // this is the first time we update the chart
2561 // let's assign colors to all dimensions
2562 if(this.library.track_colors() === true) {
2563 keys = Object.keys(this.chart.dimensions);
2565 for(i = 0; i < len ;i++)
2566 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2569 // we will re-generate the colors for the chart
2570 // based on the selected dimensions
2573 if(this.debug === true)
2574 this.log('updating Legend DOM');
2576 // mark all dimensions as invalid
2577 this.dimensions_visibility.invalidateAll();
2579 var genLabel = function(state, parent, dim, name, count) {
2580 var color = state._chartDimensionColor(name);
2582 var user_element = null;
2583 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2584 if(user_id === null)
2585 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2586 if(user_id !== null) {
2587 user_element = document.getElementById(user_id) || null;
2588 if (user_element === null)
2589 state.log('Cannot find element with id: ' + user_id);
2592 state.element_legend_childs.series[name] = {
2593 name: document.createElement('span'),
2594 value: document.createElement('span'),
2597 last_shown_value: null
2600 var label = state.element_legend_childs.series[name];
2602 // create the dimension visibility tracking for this label
2603 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2605 var rgb = NETDATA.colorHex2Rgb(color);
2606 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2607 + state.chart.chart_type
2608 + '" style="background-color: '
2609 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2610 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2612 var text = document.createTextNode(' ' + name);
2613 label.name.appendChild(text);
2616 parent.appendChild(document.createElement('br'));
2618 parent.appendChild(label.name);
2619 parent.appendChild(label.value);
2622 var content = document.createElement('div');
2624 if(this.hasLegend()) {
2625 this.element_legend_childs = {
2627 resize_handler: document.createElement('div'),
2628 toolbox: document.createElement('div'),
2629 toolbox_left: document.createElement('div'),
2630 toolbox_right: document.createElement('div'),
2631 toolbox_reset: document.createElement('div'),
2632 toolbox_zoomin: document.createElement('div'),
2633 toolbox_zoomout: document.createElement('div'),
2634 toolbox_volume: document.createElement('div'),
2635 title_date: document.createElement('span'),
2636 title_time: document.createElement('span'),
2637 title_units: document.createElement('span'),
2638 perfect_scroller: document.createElement('div'),
2642 this.element_legend.innerHTML = '';
2644 if(this.library.toolboxPanAndZoom !== null) {
2646 var get_pan_and_zoom_step = function(event) {
2648 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2650 else if (event.shiftKey)
2651 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2653 else if (event.altKey)
2654 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2657 return NETDATA.options.current.pan_and_zoom_factor;
2660 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2661 this.element.appendChild(this.element_legend_childs.toolbox);
2663 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2664 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2665 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2666 this.element_legend_childs.toolbox_left.onclick = function(e) {
2669 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2670 var before = that.view_before - step;
2671 var after = that.view_after - step;
2672 if(after >= that.netdata_first)
2673 that.library.toolboxPanAndZoom(that, after, before);
2675 if(NETDATA.options.current.show_help === true)
2676 $(this.element_legend_childs.toolbox_left).popover({
2681 placement: 'bottom',
2682 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2684 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>'
2688 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2689 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2690 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2691 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2693 NETDATA.resetAllCharts(that);
2695 if(NETDATA.options.current.show_help === true)
2696 $(this.element_legend_childs.toolbox_reset).popover({
2701 placement: 'bottom',
2702 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2703 title: 'Chart Reset',
2704 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>'
2707 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2708 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2709 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2710 this.element_legend_childs.toolbox_right.onclick = function(e) {
2712 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2713 var before = that.view_before + step;
2714 var after = that.view_after + step;
2715 if(before <= that.netdata_last)
2716 that.library.toolboxPanAndZoom(that, after, before);
2718 if(NETDATA.options.current.show_help === true)
2719 $(this.element_legend_childs.toolbox_right).popover({
2724 placement: 'bottom',
2725 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2727 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>'
2731 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2732 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2733 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2734 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2736 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2737 var before = that.view_before - dt;
2738 var after = that.view_after + dt;
2739 that.library.toolboxPanAndZoom(that, after, before);
2741 if(NETDATA.options.current.show_help === true)
2742 $(this.element_legend_childs.toolbox_zoomin).popover({
2747 placement: 'bottom',
2748 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2749 title: 'Chart Zoom In',
2750 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>'
2753 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2754 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2755 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2756 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2758 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);
2759 var before = that.view_before + dt;
2760 var after = that.view_after - dt;
2762 that.library.toolboxPanAndZoom(that, after, before);
2764 if(NETDATA.options.current.show_help === true)
2765 $(this.element_legend_childs.toolbox_zoomout).popover({
2770 placement: 'bottom',
2771 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2772 title: 'Chart Zoom Out',
2773 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>'
2776 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2777 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2778 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2779 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2780 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2781 //e.preventDefault();
2782 //alert('clicked toolbox_volume on ' + that.id);
2786 this.element_legend_childs.toolbox = null;
2787 this.element_legend_childs.toolbox_left = null;
2788 this.element_legend_childs.toolbox_reset = null;
2789 this.element_legend_childs.toolbox_right = null;
2790 this.element_legend_childs.toolbox_zoomin = null;
2791 this.element_legend_childs.toolbox_zoomout = null;
2792 this.element_legend_childs.toolbox_volume = null;
2795 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2796 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2797 this.element.appendChild(this.element_legend_childs.resize_handler);
2798 if(NETDATA.options.current.show_help === true)
2799 $(this.element_legend_childs.resize_handler).popover({
2804 placement: 'bottom',
2805 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2806 title: 'Chart Resize',
2807 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>'
2811 this.element_legend_childs.resize_handler.onmousedown =
2813 that.resizeHandler(e);
2817 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2818 that.resizeHandler(e);
2821 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2822 this.element_legend.appendChild(this.element_legend_childs.title_date);
2823 this.__last_shown_legend_date = undefined;
2825 this.element_legend.appendChild(document.createElement('br'));
2827 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2828 this.element_legend.appendChild(this.element_legend_childs.title_time);
2829 this.__last_shown_legend_time = undefined;
2831 this.element_legend.appendChild(document.createElement('br'));
2833 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2834 this.element_legend.appendChild(this.element_legend_childs.title_units);
2835 this.__last_shown_legend_units = undefined;
2837 this.element_legend.appendChild(document.createElement('br'));
2839 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2840 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2842 content.className = 'netdata-legend-series-content';
2843 this.element_legend_childs.perfect_scroller.appendChild(content);
2845 if(NETDATA.options.current.show_help === true)
2846 $(content).popover({
2851 placement: 'bottom',
2852 title: 'Chart Legend',
2853 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2854 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>'
2858 this.element_legend_childs = {
2860 resize_handler: null,
2863 toolbox_right: null,
2864 toolbox_reset: null,
2865 toolbox_zoomin: null,
2866 toolbox_zoomout: null,
2867 toolbox_volume: null,
2871 perfect_scroller: null,
2877 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2878 if(this.debug === true)
2879 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2881 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2882 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2887 keys = Object.keys(this.chart.dimensions);
2888 for(i = 0, len = keys.length; i < len ;i++) {
2890 tmp.push(this.chart.dimensions[dim].name);
2891 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2893 this.element_legend_childs.series.labels_key = tmp.toString();
2894 if(this.debug === true)
2895 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2898 // create a hidden div to be used for hidding
2899 // the original legend of the chart library
2900 var el = document.createElement('div');
2901 if(this.element_legend !== null)
2902 this.element_legend.appendChild(el);
2903 el.style.display = 'none';
2905 this.element_legend_childs.hidden = document.createElement('div');
2906 el.appendChild(this.element_legend_childs.hidden);
2908 if(this.element_legend_childs.perfect_scroller !== null) {
2909 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2911 wheelPropagation: true,
2912 swipePropagation: true,
2913 minScrollbarLength: null,
2914 maxScrollbarLength: null,
2915 useBothWheelAxes: false,
2916 suppressScrollX: true,
2917 suppressScrollY: false,
2918 scrollXMarginOffset: 0,
2919 scrollYMarginOffset: 0,
2922 Ps.update(this.element_legend_childs.perfect_scroller);
2925 this.legendShowLatestValues();
2928 this.hasLegend = function() {
2929 if(typeof this.___hasLegendCache___ !== 'undefined')
2930 return this.___hasLegendCache___;
2933 if(this.library && this.library.legend(this) === 'right-side') {
2934 var legend = $(this.element).data('legend') || 'yes';
2935 if(legend === 'yes') leg = true;
2938 this.___hasLegendCache___ = leg;
2942 this.legendWidth = function() {
2943 return (this.hasLegend())?140:0;
2946 this.legendHeight = function() {
2947 return $(this.element).height();
2950 this.chartWidth = function() {
2951 return $(this.element).width() - this.legendWidth();
2954 this.chartHeight = function() {
2955 return $(this.element).height();
2958 this.chartPixelsPerPoint = function() {
2959 // force an options provided detail
2960 var px = this.pixels_per_point;
2962 if(this.library && px < this.library.pixels_per_point(this))
2963 px = this.library.pixels_per_point(this);
2965 if(px < NETDATA.options.current.pixels_per_point)
2966 px = NETDATA.options.current.pixels_per_point;
2971 this.needsRecreation = function() {
2973 this.chart_created === true
2975 && this.library.autoresize() === false
2976 && this.tm.last_resized < NETDATA.options.last_resized
2980 this.chartURL = function() {
2981 var after, before, points_multiplier = 1;
2982 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2983 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2985 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2986 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2987 this.view_after = after * 1000;
2988 this.view_before = before * 1000;
2990 this.requested_padding = null;
2991 points_multiplier = 1;
2993 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2994 this.tm.pan_and_zoom_seq = 0;
2996 before = Math.round(this.current.force_before_ms / 1000);
2997 after = Math.round(this.current.force_after_ms / 1000);
2998 this.view_after = after * 1000;
2999 this.view_before = before * 1000;
3001 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
3002 this.requested_padding = Math.round((before - after) / 2);
3003 after -= this.requested_padding;
3004 before += this.requested_padding;
3005 this.requested_padding *= 1000;
3006 points_multiplier = 2;
3009 this.current.force_before_ms = null;
3010 this.current.force_after_ms = null;
3013 this.tm.pan_and_zoom_seq = 0;
3015 before = this.before;
3017 this.view_after = after * 1000;
3018 this.view_before = before * 1000;
3020 this.requested_padding = null;
3021 points_multiplier = 1;
3024 this.requested_after = after * 1000;
3025 this.requested_before = before * 1000;
3027 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3029 // build the data URL
3030 this.data_url = this.host + this.chart.data_url;
3031 this.data_url += "&format=" + this.library.format();
3032 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
3033 this.data_url += "&group=" + this.method;
3035 if(this.override_options !== null)
3036 this.data_url += "&options=" + this.override_options.toString();
3038 this.data_url += "&options=" + this.library.options(this);
3040 this.data_url += '|jsonwrap';
3042 if(NETDATA.options.current.eliminate_zero_dimensions === true)
3043 this.data_url += '|nonzero';
3045 if(this.append_options !== null)
3046 this.data_url += '|' + this.append_options.toString();
3049 this.data_url += "&after=" + after.toString();
3052 this.data_url += "&before=" + before.toString();
3055 this.data_url += "&dimensions=" + this.dimensions;
3057 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3058 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3061 this.redrawChart = function() {
3062 if(this.data !== null)
3063 this.updateChartWithData(this.data);
3066 this.updateChartWithData = function(data) {
3067 if(this.debug === true)
3068 this.log('updateChartWithData() called.');
3070 // this may force the chart to be re-created
3074 this.updates_counter++;
3075 this.updates_since_last_unhide++;
3076 this.updates_since_last_creation++;
3078 var started = Date.now();
3080 // if the result is JSON, find the latest update-every
3081 this.data_update_every = data.view_update_every * 1000;
3082 this.data_after = data.after * 1000;
3083 this.data_before = data.before * 1000;
3084 this.netdata_first = data.first_entry * 1000;
3085 this.netdata_last = data.last_entry * 1000;
3086 this.data_points = data.points;
3089 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3090 if(this.view_after < this.data_after) {
3091 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3092 this.view_after = this.data_after;
3095 if(this.view_before > this.data_before) {
3096 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3097 this.view_before = this.data_before;
3101 this.view_after = this.data_after;
3102 this.view_before = this.data_before;
3105 if(this.debug === true) {
3106 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3108 if(this.current.force_after_ms)
3109 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3111 this.log('STATUS: forced : unset');
3113 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3114 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3115 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3116 this.log('STATUS: points : ' + (this.data_points).toString());
3119 if(this.data_points === 0) {
3124 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3125 if(this.debug === true)
3126 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3128 this.chart_created = false;
3131 // check and update the legend
3132 this.legendUpdateDOM();
3134 if(this.chart_created === true
3135 && typeof this.library.update === 'function') {
3137 if(this.debug === true)
3138 this.log('updating chart...');
3140 if(callChartLibraryUpdateSafely(data) === false)
3144 if(this.debug === true)
3145 this.log('creating chart...');
3147 if(callChartLibraryCreateSafely(data) === false)
3151 this.legendShowLatestValues();
3152 if(this.selected === true)
3153 NETDATA.globalSelectionSync.stop();
3155 // update the performance counters
3156 var now = Date.now();
3157 this.tm.last_updated = now;
3159 // don't update last_autorefreshed if this chart is
3160 // forced to be updated with global PanAndZoom
3161 if(NETDATA.globalPanAndZoom.isActive())
3162 this.tm.last_autorefreshed = 0;
3164 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3165 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3167 this.tm.last_autorefreshed = now;
3170 this.refresh_dt_ms = now - started;
3171 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3173 if(this.refresh_dt_element !== null)
3174 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3177 this.updateChart = function(callback) {
3178 if(this.debug === true)
3179 this.log('updateChart() called.');
3181 if(this._updating === true) {
3182 if(this.debug === true)
3183 this.log('I am already updating...');
3185 if(typeof callback === 'function')
3191 // due to late initialization of charts and libraries
3192 // we need to check this too
3193 if(this.enabled === false) {
3194 if(this.debug === true)
3195 this.log('I am not enabled');
3197 if(typeof callback === 'function')
3203 if(canBeRendered() === false) {
3204 if(typeof callback === 'function')
3210 if(this.chart === null)
3211 return this.getChart(function() {
3212 return that.updateChart(callback);
3215 if(this.library.initialized === false) {
3216 if(this.library.enabled === true) {
3217 return this.library.initialize(function () {
3218 return that.updateChart(callback);
3222 error('chart library "' + this.library_name + '" is not available.');
3224 if(typeof callback === 'function')
3231 this.clearSelection();
3234 if(this.debug === true)
3235 this.log('updating from ' + this.data_url);
3237 NETDATA.statistics.refreshes_total++;
3238 NETDATA.statistics.refreshes_active++;
3240 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3241 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3243 this._updating = true;
3245 this.xhr = $.ajax( {
3250 'Cache-Control': 'no-cache, no-store',
3251 'Pragma': 'no-cache'
3253 xhrFields: { withCredentials: true } // required for the cookie
3255 .done(function(data) {
3256 that.xhr = undefined;
3257 that.retries_on_data_failures = 0;
3259 if(that.debug === true)
3260 that.log('data received. updating chart.');
3262 that.updateChartWithData(data);
3264 .fail(function(msg) {
3265 that.xhr = undefined;
3267 if(msg.statusText !== 'abort') {
3268 that.retries_on_data_failures++;
3269 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3270 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3271 that.retries_on_data_failures = 0;
3272 error('data download failed for url: ' + that.data_url);
3275 that.tm.last_autorefreshed = Date.now();
3276 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3280 .always(function() {
3281 that.xhr = undefined;
3283 NETDATA.statistics.refreshes_active--;
3284 that._updating = false;
3286 if(typeof callback === 'function')
3291 this.isVisible = function(nocache) {
3292 if(typeof nocache === 'undefined')
3295 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3297 // caching - we do not evaluate the charts visibility
3298 // if the page has not been scrolled since the last check
3299 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3300 return this.___isVisible___;
3302 this.tm.last_visible_check = Date.now();
3304 var wh = window.innerHeight;
3305 var x = this.element.getBoundingClientRect();
3309 if(x.width === 0 || x.height === 0) {
3311 this.___isVisible___ = false;
3312 return this.___isVisible___;
3315 if(x.top < 0 && -x.top > x.height) {
3316 // the chart is entirely above
3317 ret = -x.top - x.height;
3319 else if(x.top > wh) {
3320 // the chart is entirely below
3324 if(ret > tolerance) {
3325 // the chart is too far
3328 this.___isVisible___ = false;
3329 return this.___isVisible___;
3332 // the chart is inside or very close
3335 this.___isVisible___ = true;
3336 return this.___isVisible___;
3340 this.isAutoRefreshable = function() {
3341 return (this.current.autorefresh);
3344 this.canBeAutoRefreshed = function() {
3345 var now = Date.now();
3347 if(this.running === true) {
3348 if(this.debug === true)
3349 this.log('I am already running');
3354 if(this.enabled === false) {
3355 if(this.debug === true)
3356 this.log('I am not enabled');
3361 if(this.library === null || this.library.enabled === false) {
3362 error('charting library "' + this.library_name + '" is not available');
3363 if(this.debug === true)
3364 this.log('My chart library ' + this.library_name + ' is not available');
3369 if(this.isVisible() === false) {
3370 if(NETDATA.options.debug.visibility === true || this.debug === true)
3371 this.log('I am not visible');
3376 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3377 if(this.debug === true)
3378 this.log('timed force update detected - allowing this update');
3380 this.current.force_update_at = 0;
3384 if(this.isAutoRefreshable() === true) {
3385 // allow the first update, even if the page is not visible
3386 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3387 if(NETDATA.options.debug.focus === true || this.debug === true)
3388 this.log('canBeAutoRefreshed(): page does not have focus');
3393 if(this.needsRecreation() === true) {
3394 if(this.debug === true)
3395 this.log('canBeAutoRefreshed(): needs re-creation.');
3400 // options valid only for autoRefresh()
3401 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3402 if(NETDATA.globalPanAndZoom.isActive()) {
3403 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3404 if(this.debug === true)
3405 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3410 if(this.debug === true)
3411 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3417 if(this.selected === true) {
3418 if(this.debug === true)
3419 this.log('canBeAutoRefreshed(): I have a selection in place.');
3424 if(this.paused === true) {
3425 if(this.debug === true)
3426 this.log('canBeAutoRefreshed(): I am paused.');
3431 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3432 if(this.debug === true)
3433 this.log('canBeAutoRefreshed(): It is time to update me.');
3443 this.autoRefresh = function(callback) {
3444 if(this.canBeAutoRefreshed() === true && this.running === false) {
3447 state.running = true;
3448 state.updateChart(function() {
3449 state.running = false;
3451 if(typeof callback !== 'undefined')
3456 if(typeof callback !== 'undefined')
3461 this._defaultsFromDownloadedChart = function(chart) {
3463 this.chart_url = chart.url;
3464 this.data_update_every = chart.update_every * 1000;
3465 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3466 this.tm.last_info_downloaded = Date.now();
3468 if(this.title === null)
3469 this.title = chart.title;
3471 if(this.units === null)
3472 this.units = chart.units;
3475 // fetch the chart description from the netdata server
3476 this.getChart = function(callback) {
3477 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3479 this._defaultsFromDownloadedChart(this.chart);
3481 if(typeof callback === 'function')
3485 this.chart_url = "/api/v1/chart?chart=" + this.id;
3487 if(this.debug === true)
3488 this.log('downloading ' + this.chart_url);
3491 url: this.host + this.chart_url,
3494 xhrFields: { withCredentials: true } // required for the cookie
3496 .done(function(chart) {
3497 chart.url = that.chart_url;
3498 that._defaultsFromDownloadedChart(chart);
3499 NETDATA.chartRegistry.add(that.host, that.id, chart);
3502 NETDATA.error(404, that.chart_url);
3503 error('chart not found on url "' + that.chart_url + '"');
3505 .always(function() {
3506 if(typeof callback === 'function')
3512 // ============================================================================================================
3518 NETDATA.resetAllCharts = function(state) {
3519 // first clear the global selection sync
3520 // to make sure no chart is in selected state
3521 state.globalSelectionSyncStop();
3523 // there are 2 possibilities here
3524 // a. state is the global Pan and Zoom master
3525 // b. state is not the global Pan and Zoom master
3527 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3530 // clear the global Pan and Zoom
3531 // this will also refresh the master
3532 // and unblock any charts currently mirroring the master
3533 NETDATA.globalPanAndZoom.clearMaster();
3535 // if we were not the master, reset our status too
3536 // this is required because most probably the mouse
3537 // is over this chart, blocking it from auto-refreshing
3538 if(master === false && (state.paused === true || state.selected === true))
3542 // get or create a chart state, given a DOM element
3543 NETDATA.chartState = function(element) {
3544 var state = $(element).data('netdata-state-object') || null;
3545 if(state === null) {
3546 state = new chartState(element);
3547 $(element).data('netdata-state-object', state);
3552 // ----------------------------------------------------------------------------------------------------------------
3553 // Library functions
3555 // Load a script without jquery
3556 // This is used to load jquery - after it is loaded, we use jquery
3557 NETDATA._loadjQuery = function(callback) {
3558 if(typeof jQuery === 'undefined') {
3559 if(NETDATA.options.debug.main_loop === true)
3560 console.log('loading ' + NETDATA.jQuery);
3562 var script = document.createElement('script');
3563 script.type = 'text/javascript';
3564 script.async = true;
3565 script.src = NETDATA.jQuery;
3567 // script.onabort = onError;
3568 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3569 if(typeof callback === "function")
3570 script.onload = callback;
3572 var s = document.getElementsByTagName('script')[0];
3573 s.parentNode.insertBefore(script, s);
3575 else if(typeof callback === "function")
3579 NETDATA._loadCSS = function(filename) {
3580 // don't use jQuery here
3581 // styles are loaded before jQuery
3582 // to eliminate showing an unstyled page to the user
3584 var fileref = document.createElement("link");
3585 fileref.setAttribute("rel", "stylesheet");
3586 fileref.setAttribute("type", "text/css");
3587 fileref.setAttribute("href", filename);
3589 if (typeof fileref !== 'undefined')
3590 document.getElementsByTagName("head")[0].appendChild(fileref);
3593 NETDATA.colorHex2Rgb = function(hex) {
3594 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3595 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3596 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3597 return r + r + g + g + b + b;
3600 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3602 r: parseInt(result[1], 16),
3603 g: parseInt(result[2], 16),
3604 b: parseInt(result[3], 16)
3608 NETDATA.colorLuminance = function(hex, lum) {
3609 // validate hex string
3610 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3612 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3616 // convert to decimal and change luminosity
3617 var rgb = "#", c, i;
3618 for (i = 0; i < 3; i++) {
3619 c = parseInt(hex.substr(i*2,2), 16);
3620 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3621 rgb += ("00"+c).substr(c.length);
3627 NETDATA.guid = function() {
3629 return Math.floor((1 + Math.random()) * 0x10000)
3634 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3637 NETDATA.zeropad = function(x) {
3638 if(x > -10 && x < 10) return '0' + x.toString();
3639 else return x.toString();
3642 // user function to signal us the DOM has been
3644 NETDATA.updatedDom = function() {
3645 NETDATA.options.updated_dom = true;
3648 NETDATA.ready = function(callback) {
3649 NETDATA.options.pauseCallback = callback;
3652 NETDATA.pause = function(callback) {
3653 if(typeof callback === 'function') {
3654 if (NETDATA.options.pause === true)
3657 NETDATA.options.pauseCallback = callback;
3661 NETDATA.unpause = function() {
3662 NETDATA.options.pauseCallback = null;
3663 NETDATA.options.updated_dom = true;
3664 NETDATA.options.pause = false;
3667 // ----------------------------------------------------------------------------------------------------------------
3669 // this is purely sequential charts refresher
3670 // it is meant to be autonomous
3671 NETDATA.chartRefresherNoParallel = function(index) {
3672 if(NETDATA.options.debug.main_loop === true)
3673 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3675 if(NETDATA.options.updated_dom === true) {
3676 // the dom has been updated
3677 // get the dom parts again
3678 NETDATA.parseDom(NETDATA.chartRefresher);
3681 if(index >= NETDATA.options.targets.length) {
3682 if(NETDATA.options.debug.main_loop === true)
3683 console.log('waiting to restart main loop...');
3685 NETDATA.options.auto_refresher_fast_weight = 0;
3687 setTimeout(function() {
3688 NETDATA.chartRefresher();
3689 }, NETDATA.options.current.idle_between_loops);
3692 var state = NETDATA.options.targets[index];
3694 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3695 if(NETDATA.options.debug.main_loop === true)
3696 console.log('fast rendering...');
3698 state.autoRefresh(function() {
3699 NETDATA.chartRefresherNoParallel(++index);
3703 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3704 NETDATA.options.auto_refresher_fast_weight = 0;
3706 setTimeout(function() {
3707 state.autoRefresh(function() {
3708 NETDATA.chartRefresherNoParallel(++index);
3710 }, NETDATA.options.current.idle_between_charts);
3715 NETDATA.chartRefresherWaitTime = function() {
3716 return NETDATA.options.current.idle_parallel_loops;
3719 // the default refresher
3720 NETDATA.chartRefresher = function() {
3721 // console.log('auto-refresher...');
3723 if(NETDATA.options.pause === true) {
3724 // console.log('auto-refresher is paused');
3725 setTimeout(NETDATA.chartRefresher,
3726 NETDATA.chartRefresherWaitTime());
3730 if(typeof NETDATA.options.pauseCallback === 'function') {
3731 // console.log('auto-refresher is calling pauseCallback');
3732 NETDATA.options.pause = true;
3733 NETDATA.options.pauseCallback();
3734 NETDATA.chartRefresher();
3738 if(NETDATA.options.current.parallel_refresher === false) {
3739 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3740 NETDATA.chartRefresherNoParallel(0);
3744 if(NETDATA.options.updated_dom === true) {
3745 // the dom has been updated
3746 // get the dom parts again
3747 // console.log('auto-refresher is calling parseDom()');
3748 NETDATA.parseDom(NETDATA.chartRefresher);
3753 var targets = NETDATA.options.targets;
3754 var len = targets.length;
3757 state = targets[len];
3758 if(state.isVisible() === false || state.running === true)
3761 if(state.library.initialized === false) {
3762 if(state.library.enabled === true) {
3763 state.library.initialize(NETDATA.chartRefresher);
3767 state.error('chart library "' + state.library_name + '" is not enabled.');
3771 parallel.unshift(state);
3774 if(parallel.length > 0) {
3775 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3776 // this will execute the jobs in parallel
3777 $(parallel).each(function() {
3782 // console.log('auto-refresher nothing to do');
3785 // run the next refresh iteration
3786 setTimeout(NETDATA.chartRefresher,
3787 NETDATA.chartRefresherWaitTime());
3790 NETDATA.parseDom = function(callback) {
3791 NETDATA.options.last_page_scroll = Date.now();
3792 NETDATA.options.updated_dom = false;
3794 var targets = $('div[data-netdata]'); //.filter(':visible');
3796 if(NETDATA.options.debug.main_loop === true)
3797 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3799 NETDATA.options.targets = [];
3800 var len = targets.length;
3802 // the initialization will take care of sizing
3803 // and the "loading..." message
3804 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3807 if(typeof callback === 'function')
3811 // this is the main function - where everything starts
3812 NETDATA.start = function() {
3813 // this should be called only once
3815 NETDATA.options.page_is_visible = true;
3817 $(window).blur(function() {
3818 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3819 NETDATA.options.page_is_visible = false;
3820 if(NETDATA.options.debug.focus === true)
3821 console.log('Lost Focus!');
3825 $(window).focus(function() {
3826 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3827 NETDATA.options.page_is_visible = true;
3828 if(NETDATA.options.debug.focus === true)
3829 console.log('Focus restored!');
3833 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3834 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3835 NETDATA.options.page_is_visible = false;
3836 if(NETDATA.options.debug.focus === true)
3837 console.log('Document has no focus!');
3841 // bootstrap tab switching
3842 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3844 // bootstrap modal switching
3845 var $modal = $('.modal');
3846 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3847 $modal.on('shown.bs.modal', NETDATA.onscroll);
3849 // bootstrap collapse switching
3850 var $collapse = $('.collapse');
3851 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3852 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3854 NETDATA.parseDom(NETDATA.chartRefresher);
3856 // Alarms initialization
3857 setTimeout(NETDATA.alarms.init, 1000);
3859 // Registry initialization
3860 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3862 if(typeof netdataCallback === 'function')
3866 // ----------------------------------------------------------------------------------------------------------------
3869 NETDATA.peityInitialize = function(callback) {
3870 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3872 url: NETDATA.peity_js,
3875 xhrFields: { withCredentials: true } // required for the cookie
3878 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3881 NETDATA.chartLibraries.peity.enabled = false;
3882 NETDATA.error(100, NETDATA.peity_js);
3884 .always(function() {
3885 if(typeof callback === "function")
3890 NETDATA.chartLibraries.peity.enabled = false;
3891 if(typeof callback === "function")
3896 NETDATA.peityChartUpdate = function(state, data) {
3897 state.peity_instance.innerHTML = data.result;
3899 if(state.peity_options.stroke !== state.chartColors()[0]) {
3900 state.peity_options.stroke = state.chartColors()[0];
3901 if(state.chart.chart_type === 'line')
3902 state.peity_options.fill = NETDATA.themes.current.background;
3904 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3907 $(state.peity_instance).peity('line', state.peity_options);
3911 NETDATA.peityChartCreate = function(state, data) {
3912 state.peity_instance = document.createElement('div');
3913 state.element_chart.appendChild(state.peity_instance);
3915 var self = $(state.element);
3916 state.peity_options = {
3917 stroke: NETDATA.themes.current.foreground,
3918 strokeWidth: self.data('peity-strokewidth') || 1,
3919 width: state.chartWidth(),
3920 height: state.chartHeight(),
3921 fill: NETDATA.themes.current.foreground
3924 NETDATA.peityChartUpdate(state, data);
3928 // ----------------------------------------------------------------------------------------------------------------
3931 NETDATA.sparklineInitialize = function(callback) {
3932 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3934 url: NETDATA.sparkline_js,
3937 xhrFields: { withCredentials: true } // required for the cookie
3940 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3943 NETDATA.chartLibraries.sparkline.enabled = false;
3944 NETDATA.error(100, NETDATA.sparkline_js);
3946 .always(function() {
3947 if(typeof callback === "function")
3952 NETDATA.chartLibraries.sparkline.enabled = false;
3953 if(typeof callback === "function")
3958 NETDATA.sparklineChartUpdate = function(state, data) {
3959 state.sparkline_options.width = state.chartWidth();
3960 state.sparkline_options.height = state.chartHeight();
3962 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3966 NETDATA.sparklineChartCreate = function(state, data) {
3967 var self = $(state.element);
3968 var type = self.data('sparkline-type') || 'line';
3969 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3970 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3971 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3972 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3973 var composite = self.data('sparkline-composite') || undefined;
3974 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3975 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3976 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3977 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3978 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3979 var spotColor = self.data('sparkline-spotcolor') || undefined;
3980 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3981 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3982 var spotRadius = self.data('sparkline-spotradius') || undefined;
3983 var valueSpots = self.data('sparkline-valuespots') || undefined;
3984 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3985 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3986 var lineWidth = self.data('sparkline-linewidth') || undefined;
3987 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3988 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3989 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3990 var xvalues = self.data('sparkline-xvalues') || undefined;
3991 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3992 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3993 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3994 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3995 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3996 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3997 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3998 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3999 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
4000 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
4001 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
4002 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
4003 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
4004 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
4005 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
4006 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
4007 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
4008 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
4009 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
4010 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
4011 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
4012 var animatedZooms = self.data('sparkline-animatedzooms') || false;
4014 if(spotColor === 'disable') spotColor='';
4015 if(minSpotColor === 'disable') minSpotColor='';
4016 if(maxSpotColor === 'disable') maxSpotColor='';
4018 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
4020 state.sparkline_options = {
4022 lineColor: lineColor,
4023 fillColor: fillColor,
4024 chartRangeMin: chartRangeMin,
4025 chartRangeMax: chartRangeMax,
4026 composite: composite,
4027 enableTagOptions: enableTagOptions,
4028 tagOptionPrefix: tagOptionPrefix,
4029 tagValuesAttribute: tagValuesAttribute,
4030 disableHiddenCheck: disableHiddenCheck,
4031 defaultPixelsPerValue: defaultPixelsPerValue,
4032 spotColor: spotColor,
4033 minSpotColor: minSpotColor,
4034 maxSpotColor: maxSpotColor,
4035 spotRadius: spotRadius,
4036 valueSpots: valueSpots,
4037 highlightSpotColor: highlightSpotColor,
4038 highlightLineColor: highlightLineColor,
4039 lineWidth: lineWidth,
4040 normalRangeMin: normalRangeMin,
4041 normalRangeMax: normalRangeMax,
4042 drawNormalOnTop: drawNormalOnTop,
4044 chartRangeClip: chartRangeClip,
4045 chartRangeMinX: chartRangeMinX,
4046 chartRangeMaxX: chartRangeMaxX,
4047 disableInteraction: disableInteraction,
4048 disableTooltips: disableTooltips,
4049 disableHighlight: disableHighlight,
4050 highlightLighten: highlightLighten,
4051 highlightColor: highlightColor,
4052 tooltipContainer: tooltipContainer,
4053 tooltipClassname: tooltipClassname,
4054 tooltipChartTitle: state.title,
4055 tooltipFormat: tooltipFormat,
4056 tooltipPrefix: tooltipPrefix,
4057 tooltipSuffix: tooltipSuffix,
4058 tooltipSkipNull: tooltipSkipNull,
4059 tooltipValueLookups: tooltipValueLookups,
4060 tooltipFormatFieldlist: tooltipFormatFieldlist,
4061 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4062 numberFormatter: numberFormatter,
4063 numberDigitGroupSep: numberDigitGroupSep,
4064 numberDecimalMark: numberDecimalMark,
4065 numberDigitGroupCount: numberDigitGroupCount,
4066 animatedZooms: animatedZooms,
4067 width: state.chartWidth(),
4068 height: state.chartHeight()
4071 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4075 // ----------------------------------------------------------------------------------------------------------------
4082 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4083 if(after < state.netdata_first)
4084 after = state.netdata_first;
4086 if(before > state.netdata_last)
4087 before = state.netdata_last;
4089 state.setMode('zoom');
4090 state.globalSelectionSyncStop();
4091 state.globalSelectionSyncDelay();
4092 state.dygraph_user_action = true;
4093 state.dygraph_force_zoom = true;
4094 state.updateChartPanOrZoom(after, before);
4095 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4098 NETDATA.dygraphSetSelection = function(state, t) {
4099 if(typeof state.dygraph_instance !== 'undefined') {
4100 var r = state.calculateRowForTime(t);
4102 state.dygraph_instance.setSelection(r);
4104 state.dygraph_instance.clearSelection();
4105 state.legendShowUndefined();
4112 NETDATA.dygraphClearSelection = function(state) {
4113 if(typeof state.dygraph_instance !== 'undefined') {
4114 state.dygraph_instance.clearSelection();
4119 NETDATA.dygraphSmoothInitialize = function(callback) {
4121 url: NETDATA.dygraph_smooth_js,
4124 xhrFields: { withCredentials: true } // required for the cookie
4127 NETDATA.dygraph.smooth = true;
4128 smoothPlotter.smoothing = 0.3;
4131 NETDATA.dygraph.smooth = false;
4133 .always(function() {
4134 if(typeof callback === "function")
4139 NETDATA.dygraphInitialize = function(callback) {
4140 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4142 url: NETDATA.dygraph_js,
4145 xhrFields: { withCredentials: true } // required for the cookie
4148 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4151 NETDATA.chartLibraries.dygraph.enabled = false;
4152 NETDATA.error(100, NETDATA.dygraph_js);
4154 .always(function() {
4155 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4156 NETDATA.dygraphSmoothInitialize(callback);
4157 else if(typeof callback === "function")
4162 NETDATA.chartLibraries.dygraph.enabled = false;
4163 if(typeof callback === "function")
4168 NETDATA.dygraphChartUpdate = function(state, data) {
4169 var dygraph = state.dygraph_instance;
4171 if(typeof dygraph === 'undefined')
4172 return NETDATA.dygraphChartCreate(state, data);
4174 // when the chart is not visible, and hidden
4175 // if there is a window resize, dygraph detects
4176 // its element size as 0x0.
4177 // this will make it re-appear properly
4179 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4183 file: data.result.data,
4184 colors: state.chartColors(),
4185 labels: data.result.labels,
4186 labelsDivWidth: state.chartWidth() - 70,
4187 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4190 if(state.dygraph_force_zoom === true) {
4191 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4192 state.log('dygraphChartUpdate() forced zoom update');
4194 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4195 options.isZoomedIgnoreProgrammaticZoom = true;
4196 state.dygraph_force_zoom = false;
4198 else if(state.current.name !== 'auto') {
4199 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4200 state.log('dygraphChartUpdate() loose update');
4203 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4204 state.log('dygraphChartUpdate() strict update');
4206 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4207 options.isZoomedIgnoreProgrammaticZoom = true;
4210 options.valueRange = state.dygraph_options.valueRange;
4212 var oldMax = null, oldMin = null;
4213 if(state.__commonMin !== null) {
4214 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4215 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4217 if(state.__commonMax !== null) {
4218 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4219 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4222 if(state.dygraph_smooth_eligible === true) {
4223 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4224 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4225 NETDATA.dygraphChartCreate(state, data);
4230 dygraph.updateOptions(options);
4233 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4234 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4235 options.valueRange[0] = NETDATA.commonMin.get(state);
4238 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4239 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4240 options.valueRange[1] = NETDATA.commonMax.get(state);
4244 if(redraw === true) {
4245 // state.log('forcing redraw to adapt to common- min/max');
4246 dygraph.updateOptions(options);
4249 // decide the decimal points on the legend of the chart
4250 state.legendFormatValueDecimalsFromMinMax(
4251 state.dygraph_instance.axes_[0].extremeRange[0],
4252 state.dygraph_instance.axes_[0].extremeRange[1]
4255 state.dygraph_last_rendered = Date.now();
4259 NETDATA.dygraphChartCreate = function(state, data) {
4260 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4261 state.log('dygraphChartCreate()');
4263 var self = $(state.element);
4265 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4266 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4268 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4270 var smooth = (NETDATA.dygraph.smooth === true)
4271 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4274 state.dygraph_options = {
4275 colors: self.data('dygraph-colors') || state.chartColors(),
4277 // leave a few pixels empty on the right of the chart
4278 rightGap: self.data('dygraph-rightgap')
4281 showRangeSelector: self.data('dygraph-showrangeselector')
4284 showRoller: self.data('dygraph-showroller')
4287 title: self.data('dygraph-title')
4290 titleHeight: self.data('dygraph-titleheight')
4293 legend: self.data('dygraph-legend')
4294 || 'always', // we need this to get selection events
4296 labels: data.result.labels,
4298 labelsDiv: self.data('dygraph-labelsdiv')
4299 || state.element_legend_childs.hidden,
4301 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4302 || { 'fontSize':'1px' },
4304 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4305 || state.chartWidth() - 70,
4307 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4310 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4316 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4319 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4322 includeZero: self.data('dygraph-includezero')
4323 || (chart_type === 'stacked'),
4325 xRangePad: self.data('dygraph-xrangepad')
4328 yRangePad: self.data('dygraph-yrangepad')
4331 valueRange: self.data('dygraph-valuerange')
4334 ylabel: state.units,
4336 yLabelWidth: self.data('dygraph-ylabelwidth')
4339 // the function to plot the chart
4342 // The width of the lines connecting data points.
4343 // This can be used to increase the contrast or some graphs.
4344 strokeWidth: self.data('dygraph-strokewidth')
4345 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4347 strokePattern: self.data('dygraph-strokepattern')
4350 // The size of the dot to draw on each point in pixels (see drawPoints).
4351 // A dot is always drawn when a point is "isolated",
4352 // i.e. there is a missing point on either side of it.
4353 // This also controls the size of those dots.
4354 drawPoints: self.data('dygraph-drawpoints')
4357 // Draw points at the edges of gaps in the data.
4358 // This improves visibility of small data segments or other data irregularities.
4359 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4362 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4365 pointSize: self.data('dygraph-pointsize')
4368 // enabling this makes the chart with little square lines
4369 stepPlot: self.data('dygraph-stepplot')
4372 // Draw a border around graph lines to make crossing lines more easily
4373 // distinguishable. Useful for graphs with many lines.
4374 strokeBorderColor: self.data('dygraph-strokebordercolor')
4375 || NETDATA.themes.current.background,
4377 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4378 || (chart_type === 'stacked')?0.0:0.0,
4380 fillGraph: self.data('dygraph-fillgraph')
4381 || (chart_type === 'area' || chart_type === 'stacked'),
4383 fillAlpha: self.data('dygraph-fillalpha')
4384 || ((chart_type === 'stacked')
4385 ?NETDATA.options.current.color_fill_opacity_stacked
4386 :NETDATA.options.current.color_fill_opacity_area),
4388 stackedGraph: self.data('dygraph-stackedgraph')
4389 || (chart_type === 'stacked'),
4391 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4394 drawAxis: self.data('dygraph-drawaxis')
4397 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4400 axisLineColor: self.data('dygraph-axislinecolor')
4401 || NETDATA.themes.current.axis,
4403 axisLineWidth: self.data('dygraph-axislinewidth')
4406 drawGrid: self.data('dygraph-drawgrid')
4409 gridLinePattern: self.data('dygraph-gridlinepattern')
4412 gridLineWidth: self.data('dygraph-gridlinewidth')
4415 gridLineColor: self.data('dygraph-gridlinecolor')
4416 || NETDATA.themes.current.grid,
4418 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4421 sigFigs: self.data('dygraph-sigfigs')
4424 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4427 valueFormatter: self.data('dygraph-valueformatter')
4428 || function(x){ return x.toFixed(2); },
4430 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4431 || highlightCircleSize,
4433 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4434 || null, // TOO SLOW: { strokeWidth: 1.5 },
4436 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4437 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4439 pointClickCallback: self.data('dygraph-pointclickcallback')
4442 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4446 ticker: Dygraph.dateTicker,
4447 axisLabelFormatter: function (d, gran) {
4449 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4451 valueFormatter: function (ms) {
4453 //var d = new Date(ms);
4454 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4456 // no need to return anything here
4463 valueFormatter: function (x) {
4464 // we format legends with the state object
4465 // no need to do anything here
4466 // return (Math.round(x*100) / 100).toLocaleString();
4467 // return state.legendFormatValue(x);
4472 legendFormatter: function(data) {
4473 var elements = state.element_legend_childs;
4475 // if the hidden div is not there
4476 // we are not managing the legend
4477 if(elements.hidden === null) return;
4479 if (typeof data.x !== 'undefined') {
4480 state.legendSetDate(data.x);
4481 var i = data.series.length;
4483 var series = data.series[i];
4484 if(series.isVisible === true)
4485 state.legendSetLabelValue(series.label, series.y);
4487 state.legendSetLabelValue(series.label, null);
4493 drawCallback: function(dygraph, is_initial) {
4494 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4495 state.dygraph_user_action = false;
4497 var x_range = dygraph.xAxisRange();
4498 var after = Math.round(x_range[0]);
4499 var before = Math.round(x_range[1]);
4501 if(NETDATA.options.debug.dygraph === true)
4502 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4504 if(before <= state.netdata_last && after >= state.netdata_first)
4505 state.updateChartPanOrZoom(after, before);
4508 zoomCallback: function(minDate, maxDate, yRanges) {
4511 if(NETDATA.options.debug.dygraph === true)
4512 state.log('dygraphZoomCallback()');
4514 state.globalSelectionSyncStop();
4515 state.globalSelectionSyncDelay();
4516 state.setMode('zoom');
4518 // refresh it to the greatest possible zoom level
4519 state.dygraph_user_action = true;
4520 state.dygraph_force_zoom = true;
4521 state.updateChartPanOrZoom(minDate, maxDate);
4523 highlightCallback: function(event, x, points, row, seriesName) {
4526 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4527 state.log('dygraphHighlightCallback()');
4531 // there is a bug in dygraph when the chart is zoomed enough
4532 // the time it thinks is selected is wrong
4533 // here we calculate the time t based on the row number selected
4535 // var t = state.data_after + row * state.data_update_every;
4536 // 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);
4538 state.globalSelectionSync(x);
4540 // fix legend zIndex using the internal structures of dygraph legend module
4541 // this works, but it is a hack!
4542 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4544 unhighlightCallback: function(event) {
4547 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4548 state.log('dygraphUnhighlightCallback()');
4550 state.unpauseChart();
4551 state.globalSelectionSyncStop();
4553 interactionModel : {
4554 mousedown: function(event, dygraph, context) {
4555 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4556 state.log('interactionModel.mousedown()');
4558 state.dygraph_user_action = true;
4559 state.globalSelectionSyncStop();
4561 if(NETDATA.options.debug.dygraph === true)
4562 state.log('dygraphMouseDown()');
4564 // Right-click should not initiate a zoom.
4565 if(event.button && event.button === 2) return;
4567 context.initializeMouseDown(event, dygraph, context);
4569 if(event.button && event.button === 1) {
4570 if (event.altKey || event.shiftKey) {
4571 state.setMode('pan');
4572 state.globalSelectionSyncDelay();
4573 Dygraph.startPan(event, dygraph, context);
4576 state.setMode('zoom');
4577 state.globalSelectionSyncDelay();
4578 Dygraph.startZoom(event, dygraph, context);
4582 if (event.altKey || event.shiftKey) {
4583 state.setMode('zoom');
4584 state.globalSelectionSyncDelay();
4585 Dygraph.startZoom(event, dygraph, context);
4588 state.setMode('pan');
4589 state.globalSelectionSyncDelay();
4590 Dygraph.startPan(event, dygraph, context);
4594 mousemove: function(event, dygraph, context) {
4595 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4596 state.log('interactionModel.mousemove()');
4598 if(context.isPanning) {
4599 state.dygraph_user_action = true;
4600 state.globalSelectionSyncStop();
4601 state.globalSelectionSyncDelay();
4602 state.setMode('pan');
4603 context.is2DPan = false;
4604 Dygraph.movePan(event, dygraph, context);
4606 else if(context.isZooming) {
4607 state.dygraph_user_action = true;
4608 state.globalSelectionSyncStop();
4609 state.globalSelectionSyncDelay();
4610 state.setMode('zoom');
4611 Dygraph.moveZoom(event, dygraph, context);
4614 mouseup: function(event, dygraph, context) {
4615 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4616 state.log('interactionModel.mouseup()');
4618 if (context.isPanning) {
4619 state.dygraph_user_action = true;
4620 state.globalSelectionSyncDelay();
4621 Dygraph.endPan(event, dygraph, context);
4623 else if (context.isZooming) {
4624 state.dygraph_user_action = true;
4625 state.globalSelectionSyncDelay();
4626 Dygraph.endZoom(event, dygraph, context);
4629 click: function(event, dygraph, context) {
4633 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4634 state.log('interactionModel.click()');
4636 event.preventDefault();
4638 dblclick: function(event, dygraph, context) {
4643 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4644 state.log('interactionModel.dblclick()');
4645 NETDATA.resetAllCharts(state);
4647 wheel: function(event, dygraph, context) {
4650 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4651 state.log('interactionModel.wheel()');
4653 // Take the offset of a mouse event on the dygraph canvas and
4654 // convert it to a pair of percentages from the bottom left.
4655 // (Not top left, bottom is where the lower value is.)
4656 function offsetToPercentage(g, offsetX, offsetY) {
4657 // This is calculating the pixel offset of the leftmost date.
4658 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4659 var yar0 = g.yAxisRange(0);
4661 // This is calculating the pixel of the highest value. (Top pixel)
4662 var yOffset = g.toDomCoords(null, yar0[1])[1];
4664 // x y w and h are relative to the corner of the drawing area,
4665 // so that the upper corner of the drawing area is (0, 0).
4666 var x = offsetX - xOffset;
4667 var y = offsetY - yOffset;
4669 // This is computing the rightmost pixel, effectively defining the
4671 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4673 // This is computing the lowest pixel, effectively defining the height.
4674 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4676 // Percentage from the left.
4677 var xPct = w === 0 ? 0 : (x / w);
4678 // Percentage from the top.
4679 var yPct = h === 0 ? 0 : (y / h);
4681 // The (1-) part below changes it from "% distance down from the top"
4682 // to "% distance up from the bottom".
4683 return [xPct, (1-yPct)];
4686 // Adjusts [x, y] toward each other by zoomInPercentage%
4687 // Split it so the left/bottom axis gets xBias/yBias of that change and
4688 // tight/top gets (1-xBias)/(1-yBias) of that change.
4690 // If a bias is missing it splits it down the middle.
4691 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4692 xBias = xBias || 0.5;
4693 yBias = yBias || 0.5;
4695 function adjustAxis(axis, zoomInPercentage, bias) {
4696 var delta = axis[1] - axis[0];
4697 var increment = delta * zoomInPercentage;
4698 var foo = [increment * bias, increment * (1-bias)];
4700 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4703 var yAxes = g.yAxisRanges();
4705 for (var i = 0; i < yAxes.length; i++) {
4706 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4709 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4712 if(event.altKey || event.shiftKey) {
4713 state.dygraph_user_action = true;
4715 state.globalSelectionSyncStop();
4716 state.globalSelectionSyncDelay();
4718 // http://dygraphs.com/gallery/interaction-api.js
4720 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4722 normal_def = event.wheelDelta / 40;
4725 normal_def = event.deltaY * -1.2;
4727 var normal = (event.detail) ? event.detail * -1 : normal_def;
4728 var percentage = normal / 50;
4730 if (!(event.offsetX && event.offsetY)){
4731 event.offsetX = event.layerX - event.target.offsetLeft;
4732 event.offsetY = event.layerY - event.target.offsetTop;
4735 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4736 var xPct = percentages[0];
4737 var yPct = percentages[1];
4739 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4740 var after = new_x_range[0];
4741 var before = new_x_range[1];
4743 var first = state.netdata_first + state.data_update_every;
4744 var last = state.netdata_last + state.data_update_every;
4747 after -= (before - last);
4754 state.setMode('zoom');
4755 if(state.updateChartPanOrZoom(after, before) === true)
4756 dygraph.updateOptions({ dateWindow: [ after, before ] });
4758 event.preventDefault();
4761 touchstart: function(event, dygraph, context) {
4762 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4763 state.log('interactionModel.touchstart()');
4765 state.dygraph_user_action = true;
4766 state.setMode('zoom');
4769 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4771 // we overwrite the touch directions at the end, to overwrite
4772 // the internal default of dygraph
4773 context.touchDirections = { x: true, y: false };
4775 state.dygraph_last_touch_start = Date.now();
4776 state.dygraph_last_touch_move = 0;
4778 if(typeof event.touches[0].pageX === 'number')
4779 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4781 state.dygraph_last_touch_page_x = 0;
4783 touchmove: function(event, dygraph, context) {
4784 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4785 state.log('interactionModel.touchmove()');
4787 state.dygraph_user_action = true;
4788 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4790 state.dygraph_last_touch_move = Date.now();
4792 touchend: function(event, dygraph, context) {
4793 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4794 state.log('interactionModel.touchend()');
4796 state.dygraph_user_action = true;
4797 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4799 // if it didn't move, it is a selection
4800 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4801 // internal api of dygraph
4802 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4803 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4804 if(NETDATA.dygraphSetSelection(state, t) === true)
4805 state.globalSelectionSync(t);
4808 // if it was double tap within double click time, reset the charts
4809 var now = Date.now();
4810 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4811 if(state.dygraph_last_touch_move === 0) {
4812 var dt = now - state.dygraph_last_touch_end;
4813 if(dt <= NETDATA.options.current.double_click_speed)
4814 NETDATA.resetAllCharts(state);
4818 // remember the timestamp of the last touch end
4819 state.dygraph_last_touch_end = now;
4824 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4825 state.dygraph_options.drawGrid = false;
4826 state.dygraph_options.drawAxis = false;
4827 state.dygraph_options.title = undefined;
4828 state.dygraph_options.ylabel = undefined;
4829 state.dygraph_options.yLabelWidth = 0;
4830 state.dygraph_options.labelsDivWidth = 120;
4831 state.dygraph_options.labelsDivStyles.width = '120px';
4832 state.dygraph_options.labelsSeparateLines = true;
4833 state.dygraph_options.rightGap = 0;
4834 state.dygraph_options.yRangePad = 1;
4837 if(smooth === true) {
4838 state.dygraph_smooth_eligible = true;
4840 if(NETDATA.options.current.smooth_plot === true)
4841 state.dygraph_options.plotter = smoothPlotter;
4843 else state.dygraph_smooth_eligible = false;
4845 state.dygraph_instance = new Dygraph(state.element_chart,
4846 data.result.data, state.dygraph_options);
4848 state.dygraph_force_zoom = false;
4849 state.dygraph_user_action = false;
4850 state.dygraph_last_rendered = Date.now();
4852 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4853 state.__commonMin = self.data('common-min') || null;
4854 state.__commonMax = self.data('common-max') || null;
4857 state.log('incompatible version of Dygraph detected');
4858 state.__commonMin = null;
4859 state.__commonMax = null;
4862 // decide the decimal points on the legend of the chart
4863 state.legendFormatValueDecimalsFromMinMax(
4864 state.dygraph_instance.axes_[0].extremeRange[0],
4865 state.dygraph_instance.axes_[0].extremeRange[1]
4871 // ----------------------------------------------------------------------------------------------------------------
4874 NETDATA.morrisInitialize = function(callback) {
4875 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4877 // morris requires raphael
4878 if(!NETDATA.chartLibraries.raphael.initialized) {
4879 if(NETDATA.chartLibraries.raphael.enabled) {
4880 NETDATA.raphaelInitialize(function() {
4881 NETDATA.morrisInitialize(callback);
4885 NETDATA.chartLibraries.morris.enabled = false;
4886 if(typeof callback === "function")
4891 NETDATA._loadCSS(NETDATA.morris_css);
4894 url: NETDATA.morris_js,
4897 xhrFields: { withCredentials: true } // required for the cookie
4900 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4903 NETDATA.chartLibraries.morris.enabled = false;
4904 NETDATA.error(100, NETDATA.morris_js);
4906 .always(function() {
4907 if(typeof callback === "function")
4913 NETDATA.chartLibraries.morris.enabled = false;
4914 if(typeof callback === "function")
4919 NETDATA.morrisChartUpdate = function(state, data) {
4920 state.morris_instance.setData(data.result.data);
4924 NETDATA.morrisChartCreate = function(state, data) {
4926 state.morris_options = {
4927 element: state.element_chart.id,
4928 data: data.result.data,
4930 ykeys: data.dimension_names,
4931 labels: data.dimension_names,
4937 continuousLine: false,
4938 behaveLikeLine: false
4941 if(state.chart.chart_type === 'line')
4942 state.morris_instance = new Morris.Line(state.morris_options);
4944 else if(state.chart.chart_type === 'area') {
4945 state.morris_options.behaveLikeLine = true;
4946 state.morris_instance = new Morris.Area(state.morris_options);
4949 state.morris_instance = new Morris.Area(state.morris_options);
4954 // ----------------------------------------------------------------------------------------------------------------
4957 NETDATA.raphaelInitialize = function(callback) {
4958 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4960 url: NETDATA.raphael_js,
4963 xhrFields: { withCredentials: true } // required for the cookie
4966 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4969 NETDATA.chartLibraries.raphael.enabled = false;
4970 NETDATA.error(100, NETDATA.raphael_js);
4972 .always(function() {
4973 if(typeof callback === "function")
4978 NETDATA.chartLibraries.raphael.enabled = false;
4979 if(typeof callback === "function")
4984 NETDATA.raphaelChartUpdate = function(state, data) {
4985 $(state.element_chart).raphael(data.result, {
4986 width: state.chartWidth(),
4987 height: state.chartHeight()
4993 NETDATA.raphaelChartCreate = function(state, data) {
4994 $(state.element_chart).raphael(data.result, {
4995 width: state.chartWidth(),
4996 height: state.chartHeight()
5002 // ----------------------------------------------------------------------------------------------------------------
5005 NETDATA.c3Initialize = function(callback) {
5006 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
5009 if(!NETDATA.chartLibraries.d3.initialized) {
5010 if(NETDATA.chartLibraries.d3.enabled) {
5011 NETDATA.d3Initialize(function() {
5012 NETDATA.c3Initialize(callback);
5016 NETDATA.chartLibraries.c3.enabled = false;
5017 if(typeof callback === "function")
5022 NETDATA._loadCSS(NETDATA.c3_css);
5028 xhrFields: { withCredentials: true } // required for the cookie
5031 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
5034 NETDATA.chartLibraries.c3.enabled = false;
5035 NETDATA.error(100, NETDATA.c3_js);
5037 .always(function() {
5038 if(typeof callback === "function")
5044 NETDATA.chartLibraries.c3.enabled = false;
5045 if(typeof callback === "function")
5050 NETDATA.c3ChartUpdate = function(state, data) {
5051 state.c3_instance.destroy();
5052 return NETDATA.c3ChartCreate(state, data);
5054 //state.c3_instance.load({
5055 // rows: data.result,
5062 NETDATA.c3ChartCreate = function(state, data) {
5064 state.element_chart.id = 'c3-' + state.uuid;
5065 // console.log('id = ' + state.element_chart.id);
5067 state.c3_instance = c3.generate({
5068 bindto: '#' + state.element_chart.id,
5070 width: state.chartWidth(),
5071 height: state.chartHeight()
5074 pattern: state.chartColors()
5079 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5085 format: function(x) {
5086 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5113 // console.log(state.c3_instance);
5118 // ----------------------------------------------------------------------------------------------------------------
5121 NETDATA.d3Initialize = function(callback) {
5122 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5127 xhrFields: { withCredentials: true } // required for the cookie
5130 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5133 NETDATA.chartLibraries.d3.enabled = false;
5134 NETDATA.error(100, NETDATA.d3_js);
5136 .always(function() {
5137 if(typeof callback === "function")
5142 NETDATA.chartLibraries.d3.enabled = false;
5143 if(typeof callback === "function")
5148 NETDATA.d3ChartUpdate = function(state, data) {
5155 NETDATA.d3ChartCreate = function(state, data) {
5162 // ----------------------------------------------------------------------------------------------------------------
5165 NETDATA.googleInitialize = function(callback) {
5166 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5168 url: NETDATA.google_js,
5171 xhrFields: { withCredentials: true } // required for the cookie
5174 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5175 google.load('visualization', '1.1', {
5176 'packages': ['corechart', 'controls'],
5177 'callback': callback
5181 NETDATA.chartLibraries.google.enabled = false;
5182 NETDATA.error(100, NETDATA.google_js);
5183 if(typeof callback === "function")
5188 NETDATA.chartLibraries.google.enabled = false;
5189 if(typeof callback === "function")
5194 NETDATA.googleChartUpdate = function(state, data) {
5195 var datatable = new google.visualization.DataTable(data.result);
5196 state.google_instance.draw(datatable, state.google_options);
5200 NETDATA.googleChartCreate = function(state, data) {
5201 var datatable = new google.visualization.DataTable(data.result);
5203 state.google_options = {
5204 colors: state.chartColors(),
5206 // do not set width, height - the chart resizes itself
5207 //width: state.chartWidth(),
5208 //height: state.chartHeight(),
5213 // title: "Time of Day",
5214 // format:'HH:mm:ss',
5215 viewWindowMode: 'maximized',
5227 viewWindowMode: 'pretty',
5242 focusTarget: 'category',
5249 titlePosition: 'out',
5260 curveType: 'function',
5265 switch(state.chart.chart_type) {
5267 state.google_options.vAxis.viewWindowMode = 'maximized';
5268 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5269 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5273 state.google_options.isStacked = true;
5274 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5275 state.google_options.vAxis.viewWindowMode = 'maximized';
5276 state.google_options.vAxis.minValue = null;
5277 state.google_options.vAxis.maxValue = null;
5278 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5283 state.google_options.lineWidth = 2;
5284 state.google_instance = new google.visualization.LineChart(state.element_chart);
5288 state.google_instance.draw(datatable, state.google_options);
5292 // ----------------------------------------------------------------------------------------------------------------
5294 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5295 if(typeof value !== 'number') value = 0;
5296 if(typeof min !== 'number') min = 0;
5297 if(typeof max !== 'number') max = 0;
5299 if(min > value) min = value;
5300 if(max < value) max = value;
5302 // make sure it is zero based
5303 if(min > 0) min = 0;
5304 if(max < 0) max = 0;
5309 pcent = Math.round(value * 100 / max);
5310 if(pcent === 0) pcent = 0.1;
5314 pcent = Math.round(-value * 100 / min);
5315 if(pcent === 0) pcent = -0.1;
5321 // ----------------------------------------------------------------------------------------------------------------
5324 NETDATA.easypiechartInitialize = function(callback) {
5325 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5327 url: NETDATA.easypiechart_js,
5330 xhrFields: { withCredentials: true } // required for the cookie
5333 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5336 NETDATA.chartLibraries.easypiechart.enabled = false;
5337 NETDATA.error(100, NETDATA.easypiechart_js);
5339 .always(function() {
5340 if(typeof callback === "function")
5345 NETDATA.chartLibraries.easypiechart.enabled = false;
5346 if(typeof callback === "function")
5351 NETDATA.easypiechartClearSelection = function(state) {
5352 if(typeof state.easyPieChartEvent !== 'undefined') {
5353 if(state.easyPieChartEvent.timer !== undefined) {
5354 clearTimeout(state.easyPieChartEvent.timer);
5357 state.easyPieChartEvent.timer = undefined;
5360 if(state.isAutoRefreshable() === true && state.data !== null) {
5361 NETDATA.easypiechartChartUpdate(state, state.data);
5364 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5365 state.easyPieChart_instance.update(0);
5367 state.easyPieChart_instance.enableAnimation();
5372 NETDATA.easypiechartSetSelection = function(state, t) {
5373 if(state.timeIsVisible(t) !== true)
5374 return NETDATA.easypiechartClearSelection(state);
5376 var slot = state.calculateRowForTime(t);
5377 if(slot < 0 || slot >= state.data.result.length)
5378 return NETDATA.easypiechartClearSelection(state);
5380 if(typeof state.easyPieChartEvent === 'undefined') {
5381 state.easyPieChartEvent = {
5388 var value = state.data.result[state.data.result.length - 1 - slot];
5389 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5390 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5391 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5393 state.easyPieChartEvent.value = value;
5394 state.easyPieChartEvent.pcent = pcent;
5395 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5397 if(state.easyPieChartEvent.timer === undefined) {
5398 state.easyPieChart_instance.disableAnimation();
5400 state.easyPieChartEvent.timer = setTimeout(function() {
5401 state.easyPieChartEvent.timer = undefined;
5402 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5403 }, NETDATA.options.current.charts_selection_animation_delay);
5409 NETDATA.easypiechartChartUpdate = function(state, data) {
5410 var value, min, max, pcent;
5412 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5417 value = data.result[0];
5418 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5419 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5420 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5423 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5424 state.easyPieChart_instance.update(pcent);
5428 NETDATA.easypiechartChartCreate = function(state, data) {
5429 var self = $(state.element);
5430 var chart = $(state.element_chart);
5432 var value = data.result[0];
5433 var min = self.data('easypiechart-min-value') || null;
5434 var max = self.data('easypiechart-max-value') || null;
5435 var adjust = self.data('easypiechart-adjust') || null;
5438 min = NETDATA.commonMin.get(state);
5439 state.easyPieChartMin = null;
5442 state.easyPieChartMin = min;
5445 max = NETDATA.commonMax.get(state);
5446 state.easyPieChartMax = null;
5449 state.easyPieChartMax = max;
5451 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5453 chart.data('data-percent', pcent);
5457 case 'width': size = state.chartHeight(); break;
5458 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5459 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5461 default: size = state.chartWidth(); break;
5463 state.element.style.width = size + 'px';
5464 state.element.style.height = size + 'px';
5466 var stroke = Math.floor(size / 22);
5467 if(stroke < 3) stroke = 2;
5469 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5470 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5471 state.easyPieChartLabel = document.createElement('span');
5472 state.easyPieChartLabel.className = 'easyPieChartLabel';
5473 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5474 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5475 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5476 state.element_chart.appendChild(state.easyPieChartLabel);
5478 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5479 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5480 state.easyPieChartTitle = document.createElement('span');
5481 state.easyPieChartTitle.className = 'easyPieChartTitle';
5482 state.easyPieChartTitle.innerText = state.title;
5483 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5484 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5485 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5486 state.element_chart.appendChild(state.easyPieChartTitle);
5488 var unitfontsize = Math.round(titlefontsize * 0.9);
5489 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5490 state.easyPieChartUnits = document.createElement('span');
5491 state.easyPieChartUnits.className = 'easyPieChartUnits';
5492 state.easyPieChartUnits.innerText = state.units;
5493 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5494 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5495 state.element_chart.appendChild(state.easyPieChartUnits);
5497 var barColor = self.data('easypiechart-barcolor');
5498 if(typeof barColor === 'undefined' || barColor === null)
5499 barColor = state.chartColors()[0];
5501 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5502 var tmp = eval(barColor);
5503 if(typeof tmp === 'function')
5507 chart.easyPieChart({
5509 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5510 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5511 scaleLength: self.data('easypiechart-scalelength') || 5,
5512 lineCap: self.data('easypiechart-linecap') || 'round',
5513 lineWidth: self.data('easypiechart-linewidth') || stroke,
5514 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5515 size: self.data('easypiechart-size') || size,
5516 rotate: self.data('easypiechart-rotate') || 0,
5517 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5518 easing: self.data('easypiechart-easing') || undefined
5521 // when we just re-create the chart
5522 // do not animate the first update
5524 if(typeof state.easyPieChart_instance !== 'undefined')
5527 state.easyPieChart_instance = chart.data('easyPieChart');
5528 if(animate === false) state.easyPieChart_instance.disableAnimation();
5529 state.easyPieChart_instance.update(pcent);
5530 if(animate === false) state.easyPieChart_instance.enableAnimation();
5534 // ----------------------------------------------------------------------------------------------------------------
5537 NETDATA.gaugeInitialize = function(callback) {
5538 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5540 url: NETDATA.gauge_js,
5543 xhrFields: { withCredentials: true } // required for the cookie
5546 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5549 NETDATA.chartLibraries.gauge.enabled = false;
5550 NETDATA.error(100, NETDATA.gauge_js);
5552 .always(function() {
5553 if(typeof callback === "function")
5558 NETDATA.chartLibraries.gauge.enabled = false;
5559 if(typeof callback === "function")
5564 NETDATA.gaugeAnimation = function(state, status) {
5567 if(typeof status === 'boolean' && status === false)
5569 else if(typeof status === 'number')
5572 // console.log('gauge speed ' + speed);
5573 state.gauge_instance.animationSpeed = speed;
5574 state.___gaugeOld__.speed = speed;
5577 NETDATA.gaugeSet = function(state, value, min, max) {
5578 if(typeof value !== 'number') value = 0;
5579 if(typeof min !== 'number') min = 0;
5580 if(typeof max !== 'number') max = 0;
5581 if(value > max) max = value;
5582 if(value < min) min = value;
5588 else if(min === max)
5591 // gauge.js has an issue if the needle
5592 // is smaller than min or larger than max
5593 // when we set the new values
5594 // the needle will go crazy
5596 // to prevent it, we always feed it
5597 // with a percentage, so that the needle
5598 // is always between min and max
5599 var pcent = (value - min) * 100 / (max - min);
5601 // bug fix for gauge.js 1.3.1
5602 // if the value is the absolute min or max, the chart is broken
5603 if(pcent < 0.001) pcent = 0.001;
5604 if(pcent > 99.999) pcent = 99.999;
5606 state.gauge_instance.set(pcent);
5607 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5609 state.___gaugeOld__.value = value;
5610 state.___gaugeOld__.min = min;
5611 state.___gaugeOld__.max = max;
5614 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5615 if(state.___gaugeOld__.valueLabel !== value) {
5616 state.___gaugeOld__.valueLabel = value;
5617 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5619 if(state.___gaugeOld__.minLabel !== min) {
5620 state.___gaugeOld__.minLabel = min;
5621 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5623 if(state.___gaugeOld__.maxLabel !== max) {
5624 state.___gaugeOld__.maxLabel = max;
5625 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5629 NETDATA.gaugeClearSelection = function(state) {
5630 if(typeof state.gaugeEvent !== 'undefined') {
5631 if(state.gaugeEvent.timer !== undefined) {
5632 clearTimeout(state.gaugeEvent.timer);
5635 state.gaugeEvent.timer = undefined;
5638 if(state.isAutoRefreshable() === true && state.data !== null) {
5639 NETDATA.gaugeChartUpdate(state, state.data);
5642 NETDATA.gaugeAnimation(state, false);
5643 NETDATA.gaugeSet(state, null, null, null);
5644 NETDATA.gaugeSetLabels(state, null, null, null);
5647 NETDATA.gaugeAnimation(state, true);
5651 NETDATA.gaugeSetSelection = function(state, t) {
5652 if(state.timeIsVisible(t) !== true)
5653 return NETDATA.gaugeClearSelection(state);
5655 var slot = state.calculateRowForTime(t);
5656 if(slot < 0 || slot >= state.data.result.length)
5657 return NETDATA.gaugeClearSelection(state);
5659 if(typeof state.gaugeEvent === 'undefined') {
5660 state.gaugeEvent = {
5668 var value = state.data.result[state.data.result.length - 1 - slot];
5669 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5670 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5672 // make sure it is zero based
5673 if(min > 0) min = 0;
5674 if(max < 0) max = 0;
5676 state.gaugeEvent.value = value;
5677 state.gaugeEvent.min = min;
5678 state.gaugeEvent.max = max;
5679 NETDATA.gaugeSetLabels(state, value, min, max);
5681 if(state.gaugeEvent.timer === undefined) {
5682 NETDATA.gaugeAnimation(state, false);
5684 state.gaugeEvent.timer = setTimeout(function() {
5685 state.gaugeEvent.timer = undefined;
5686 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5687 }, NETDATA.options.current.charts_selection_animation_delay);
5693 NETDATA.gaugeChartUpdate = function(state, data) {
5694 var value, min, max;
5696 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5700 NETDATA.gaugeSetLabels(state, null, null, null);
5703 value = data.result[0];
5704 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5705 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5706 if(value < min) min = value;
5707 if(value > max) max = value;
5709 // make sure it is zero based
5710 if(min > 0) min = 0;
5711 if(max < 0) max = 0;
5713 NETDATA.gaugeSetLabels(state, value, min, max);
5716 NETDATA.gaugeSet(state, value, min, max);
5720 NETDATA.gaugeChartCreate = function(state, data) {
5721 var self = $(state.element);
5722 // var chart = $(state.element_chart);
5724 var value = data.result[0];
5725 var min = self.data('gauge-min-value') || null;
5726 var max = self.data('gauge-max-value') || null;
5727 var adjust = self.data('gauge-adjust') || null;
5728 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5729 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5730 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5731 var stopColor = self.data('gauge-stop-color') || void 0;
5732 var generateGradient = self.data('gauge-generate-gradient') || false;
5735 min = NETDATA.commonMin.get(state);
5736 state.gaugeMin = null;
5739 state.gaugeMin = min;
5742 max = NETDATA.commonMax.get(state);
5743 state.gaugeMax = null;
5746 state.gaugeMax = max;
5748 // make sure it is zero based
5749 if(min > 0) min = 0;
5750 if(max < 0) max = 0;
5752 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5754 // case 'width': width = height * ratio; break;
5756 // default: height = width / ratio; break;
5758 //state.element.style.width = width.toString() + 'px';
5759 //state.element.style.height = height.toString() + 'px';
5764 lines: 12, // The number of lines to draw
5765 angle: 0.15, // The span of the gauge arc
5766 lineWidth: 0.50, // The line thickness
5767 radiusScale: 0.85, // Relative radius
5769 length: 0.8, // 0.9 The radius of the inner circle
5770 strokeWidth: 0.035, // The rotation offset
5771 color: pointerColor // Fill color
5773 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5774 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5775 colorStart: startColor, // Colors
5776 colorStop: stopColor, // just experiment with them
5777 strokeColor: strokeColor, // to see which ones work best for you
5778 generateGradient: (generateGradient === true),
5780 highDpiSupport: true // High resolution support
5783 if (generateGradient.constructor === Array) {
5785 // data-gauge-generate-gradient="[0, 50, 100]"
5786 // data-gauge-gradient-percent-color-0="#FFFFFF"
5787 // data-gauge-gradient-percent-color-50="#999900"
5788 // data-gauge-gradient-percent-color-100="#000000"
5790 options.percentColors = [];
5791 var len = generateGradient.length;
5793 var pcent = generateGradient[len];
5794 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5795 if(color !== false) {
5799 options.percentColors.unshift(a);
5802 if(options.percentColors.length === 0)
5803 delete options.percentColors;
5805 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5806 //noinspection PointlessArithmeticExpressionJS
5807 options.percentColors = [
5808 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5809 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5810 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5811 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5812 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5813 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5814 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5815 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5816 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5817 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5818 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5821 state.gauge_canvas = document.createElement('canvas');
5822 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5823 state.gauge_canvas.className = 'gaugeChart';
5824 state.gauge_canvas.width = width;
5825 state.gauge_canvas.height = height;
5826 state.element_chart.appendChild(state.gauge_canvas);
5828 var valuefontsize = Math.floor(height / 6);
5829 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5830 state.gaugeChartLabel = document.createElement('span');
5831 state.gaugeChartLabel.className = 'gaugeChartLabel';
5832 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5833 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5834 state.element_chart.appendChild(state.gaugeChartLabel);
5836 var titlefontsize = Math.round(valuefontsize / 2);
5838 state.gaugeChartTitle = document.createElement('span');
5839 state.gaugeChartTitle.className = 'gaugeChartTitle';
5840 state.gaugeChartTitle.innerText = state.title;
5841 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5842 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5843 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5844 state.element_chart.appendChild(state.gaugeChartTitle);
5846 var unitfontsize = Math.round(titlefontsize * 0.9);
5847 state.gaugeChartUnits = document.createElement('span');
5848 state.gaugeChartUnits.className = 'gaugeChartUnits';
5849 state.gaugeChartUnits.innerText = state.units;
5850 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5851 state.element_chart.appendChild(state.gaugeChartUnits);
5853 state.gaugeChartMin = document.createElement('span');
5854 state.gaugeChartMin.className = 'gaugeChartMin';
5855 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5856 state.element_chart.appendChild(state.gaugeChartMin);
5858 state.gaugeChartMax = document.createElement('span');
5859 state.gaugeChartMax.className = 'gaugeChartMax';
5860 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5861 state.element_chart.appendChild(state.gaugeChartMax);
5863 // when we just re-create the chart
5864 // do not animate the first update
5866 if(typeof state.gauge_instance !== 'undefined')
5869 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5871 state.___gaugeOld__ = {
5880 // we will always feed a percentage
5881 state.gauge_instance.minValue = 0;
5882 state.gauge_instance.maxValue = 100;
5884 NETDATA.gaugeAnimation(state, animate);
5885 NETDATA.gaugeSet(state, value, min, max);
5886 NETDATA.gaugeSetLabels(state, value, min, max);
5887 NETDATA.gaugeAnimation(state, true);
5891 // ----------------------------------------------------------------------------------------------------------------
5892 // Charts Libraries Registration
5894 NETDATA.chartLibraries = {
5896 initialize: NETDATA.dygraphInitialize,
5897 create: NETDATA.dygraphChartCreate,
5898 update: NETDATA.dygraphChartUpdate,
5899 resize: function(state) {
5900 if(typeof state.dygraph_instance.resize === 'function')
5901 state.dygraph_instance.resize();
5903 setSelection: NETDATA.dygraphSetSelection,
5904 clearSelection: NETDATA.dygraphClearSelection,
5905 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5908 format: function(state) { void(state); return 'json'; },
5909 options: function(state) { void(state); return 'ms|flip'; },
5910 legend: function(state) {
5911 return (this.isSparkline(state) === false)?'right-side':null;
5913 autoresize: function(state) { void(state); return true; },
5914 max_updates_to_recreate: function(state) { void(state); return 5000; },
5915 track_colors: function(state) { void(state); return true; },
5916 pixels_per_point: function(state) {
5917 return (this.isSparkline(state) === false)?3:2;
5919 isSparkline: function(state) {
5920 if(typeof state.dygraph_sparkline === 'undefined') {
5921 var t = $(state.element).data('dygraph-theme');
5922 state.dygraph_sparkline = (t === 'sparkline');
5924 return state.dygraph_sparkline;
5928 initialize: NETDATA.sparklineInitialize,
5929 create: NETDATA.sparklineChartCreate,
5930 update: NETDATA.sparklineChartUpdate,
5932 setSelection: undefined, // function(state, t) { void(state); return true; },
5933 clearSelection: undefined, // function(state) { void(state); return true; },
5934 toolboxPanAndZoom: null,
5937 format: function(state) { void(state); return 'array'; },
5938 options: function(state) { void(state); return 'flip|abs'; },
5939 legend: function(state) { void(state); return null; },
5940 autoresize: function(state) { void(state); return false; },
5941 max_updates_to_recreate: function(state) { void(state); return 5000; },
5942 track_colors: function(state) { void(state); return false; },
5943 pixels_per_point: function(state) { void(state); return 3; }
5946 initialize: NETDATA.peityInitialize,
5947 create: NETDATA.peityChartCreate,
5948 update: NETDATA.peityChartUpdate,
5950 setSelection: undefined, // function(state, t) { void(state); return true; },
5951 clearSelection: undefined, // function(state) { void(state); return true; },
5952 toolboxPanAndZoom: null,
5955 format: function(state) { void(state); return 'ssvcomma'; },
5956 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5957 legend: function(state) { void(state); return null; },
5958 autoresize: function(state) { void(state); return false; },
5959 max_updates_to_recreate: function(state) { void(state); return 5000; },
5960 track_colors: function(state) { void(state); return false; },
5961 pixels_per_point: function(state) { void(state); return 3; }
5964 initialize: NETDATA.morrisInitialize,
5965 create: NETDATA.morrisChartCreate,
5966 update: NETDATA.morrisChartUpdate,
5968 setSelection: undefined, // function(state, t) { void(state); return true; },
5969 clearSelection: undefined, // function(state) { void(state); return true; },
5970 toolboxPanAndZoom: null,
5973 format: function(state) { void(state); return 'json'; },
5974 options: function(state) { void(state); return 'objectrows|ms'; },
5975 legend: function(state) { void(state); return null; },
5976 autoresize: function(state) { void(state); return false; },
5977 max_updates_to_recreate: function(state) { void(state); return 50; },
5978 track_colors: function(state) { void(state); return false; },
5979 pixels_per_point: function(state) { void(state); return 15; }
5982 initialize: NETDATA.googleInitialize,
5983 create: NETDATA.googleChartCreate,
5984 update: NETDATA.googleChartUpdate,
5986 setSelection: undefined, //function(state, t) { void(state); return true; },
5987 clearSelection: undefined, //function(state) { void(state); return true; },
5988 toolboxPanAndZoom: null,
5991 format: function(state) { void(state); return 'datatable'; },
5992 options: function(state) { void(state); return ''; },
5993 legend: function(state) { void(state); return null; },
5994 autoresize: function(state) { void(state); return false; },
5995 max_updates_to_recreate: function(state) { void(state); return 300; },
5996 track_colors: function(state) { void(state); return false; },
5997 pixels_per_point: function(state) { void(state); return 4; }
6000 initialize: NETDATA.raphaelInitialize,
6001 create: NETDATA.raphaelChartCreate,
6002 update: NETDATA.raphaelChartUpdate,
6004 setSelection: undefined, // function(state, t) { void(state); return true; },
6005 clearSelection: undefined, // function(state) { void(state); return true; },
6006 toolboxPanAndZoom: null,
6009 format: function(state) { void(state); return 'json'; },
6010 options: function(state) { void(state); return ''; },
6011 legend: function(state) { void(state); return null; },
6012 autoresize: function(state) { void(state); return false; },
6013 max_updates_to_recreate: function(state) { void(state); return 5000; },
6014 track_colors: function(state) { void(state); return false; },
6015 pixels_per_point: function(state) { void(state); return 3; }
6018 initialize: NETDATA.c3Initialize,
6019 create: NETDATA.c3ChartCreate,
6020 update: NETDATA.c3ChartUpdate,
6022 setSelection: undefined, // function(state, t) { void(state); return true; },
6023 clearSelection: undefined, // function(state) { void(state); return true; },
6024 toolboxPanAndZoom: null,
6027 format: function(state) { void(state); return 'csvjsonarray'; },
6028 options: function(state) { void(state); return 'milliseconds'; },
6029 legend: function(state) { void(state); return null; },
6030 autoresize: function(state) { void(state); return false; },
6031 max_updates_to_recreate: function(state) { void(state); return 5000; },
6032 track_colors: function(state) { void(state); return false; },
6033 pixels_per_point: function(state) { void(state); return 15; }
6036 initialize: NETDATA.d3Initialize,
6037 create: NETDATA.d3ChartCreate,
6038 update: NETDATA.d3ChartUpdate,
6040 setSelection: undefined, // function(state, t) { void(state); return true; },
6041 clearSelection: undefined, // function(state) { void(state); return true; },
6042 toolboxPanAndZoom: null,
6045 format: function(state) { void(state); return 'json'; },
6046 options: function(state) { void(state); return ''; },
6047 legend: function(state) { void(state); return null; },
6048 autoresize: function(state) { void(state); return false; },
6049 max_updates_to_recreate: function(state) { void(state); return 5000; },
6050 track_colors: function(state) { void(state); return false; },
6051 pixels_per_point: function(state) { void(state); return 3; }
6054 initialize: NETDATA.easypiechartInitialize,
6055 create: NETDATA.easypiechartChartCreate,
6056 update: NETDATA.easypiechartChartUpdate,
6058 setSelection: NETDATA.easypiechartSetSelection,
6059 clearSelection: NETDATA.easypiechartClearSelection,
6060 toolboxPanAndZoom: null,
6063 format: function(state) { void(state); return 'array'; },
6064 options: function(state) { void(state); return 'absolute'; },
6065 legend: function(state) { void(state); return null; },
6066 autoresize: function(state) { void(state); return false; },
6067 max_updates_to_recreate: function(state) { void(state); return 5000; },
6068 track_colors: function(state) { void(state); return true; },
6069 pixels_per_point: function(state) { void(state); return 3; },
6073 initialize: NETDATA.gaugeInitialize,
6074 create: NETDATA.gaugeChartCreate,
6075 update: NETDATA.gaugeChartUpdate,
6077 setSelection: NETDATA.gaugeSetSelection,
6078 clearSelection: NETDATA.gaugeClearSelection,
6079 toolboxPanAndZoom: null,
6082 format: function(state) { void(state); return 'array'; },
6083 options: function(state) { void(state); return 'absolute'; },
6084 legend: function(state) { void(state); return null; },
6085 autoresize: function(state) { void(state); return false; },
6086 max_updates_to_recreate: function(state) { void(state); return 5000; },
6087 track_colors: function(state) { void(state); return true; },
6088 pixels_per_point: function(state) { void(state); return 3; },
6093 NETDATA.registerChartLibrary = function(library, url) {
6094 if(NETDATA.options.debug.libraries === true)
6095 console.log("registering chart library: " + library);
6097 NETDATA.chartLibraries[library].url = url;
6098 NETDATA.chartLibraries[library].initialized = true;
6099 NETDATA.chartLibraries[library].enabled = true;
6102 // ----------------------------------------------------------------------------------------------------------------
6103 // Load required JS libraries and CSS
6105 NETDATA.requiredJs = [
6107 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6109 isAlreadyLoaded: function() {
6110 // check if bootstrap is loaded
6111 if(typeof $().emulateTransitionEnd === 'function')
6114 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6119 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6120 isAlreadyLoaded: function() { return false; }
6124 NETDATA.requiredCSS = [
6126 url: NETDATA.themes.current.bootstrap_css,
6127 isAlreadyLoaded: function() {
6128 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6132 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6133 isAlreadyLoaded: function() { return false; }
6136 url: NETDATA.themes.current.dashboard_css,
6137 isAlreadyLoaded: function() { return false; }
6141 NETDATA.loadedRequiredJs = 0;
6142 NETDATA.loadRequiredJs = function(index, callback) {
6143 if(index >= NETDATA.requiredJs.length) {
6144 if(typeof callback === 'function')
6149 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6150 NETDATA.loadedRequiredJs++;
6151 NETDATA.loadRequiredJs(++index, callback);
6155 if(NETDATA.options.debug.main_loop === true)
6156 console.log('loading ' + NETDATA.requiredJs[index].url);
6159 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6163 url: NETDATA.requiredJs[index].url,
6166 xhrFields: { withCredentials: true } // required for the cookie
6169 if(NETDATA.options.debug.main_loop === true)
6170 console.log('loaded ' + NETDATA.requiredJs[index].url);
6173 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6175 .always(function() {
6176 NETDATA.loadedRequiredJs++;
6179 NETDATA.loadRequiredJs(++index, callback);
6183 NETDATA.loadRequiredJs(++index, callback);
6186 NETDATA.loadRequiredCSS = function(index) {
6187 if(index >= NETDATA.requiredCSS.length)
6190 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6191 NETDATA.loadRequiredCSS(++index);
6195 if(NETDATA.options.debug.main_loop === true)
6196 console.log('loading ' + NETDATA.requiredCSS[index].url);
6198 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6199 NETDATA.loadRequiredCSS(++index);
6203 // ----------------------------------------------------------------------------------------------------------------
6204 // Registry of netdata hosts
6207 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6208 chart_div_offset: 100, // give that space above the chart when scrolling to it
6209 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6210 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6212 ms_penalty: 0, // the time penalty of the next alarm
6213 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6214 // if alarms are shown faster than: one per 500ms
6216 notifications: false, // when true, the browser supports notifications (may not be granted though)
6217 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6218 first_notification_id: 0, // the id of the first alarm_log entry for this session
6219 // this is used to prevent CLEAR notifications for past events
6220 // notifications_shown: [],
6222 server: null, // the server to connect to for fetching alarms
6223 current: null, // the list of raised alarms - updated in the background
6224 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6226 notify: function(entry) {
6227 // console.log('alarm ' + entry.unique_id);
6229 if(entry.updated === true) {
6230 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6234 var value_string = entry.value_string;
6236 if(NETDATA.alarms.current !== null) {
6237 // get the current value_string
6238 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6239 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6240 value_string = t.value_string;
6243 var name = entry.name.replace(/_/g, ' ');
6244 var status = entry.status.toLowerCase();
6245 var title = name + ' = ' + value_string.toString();
6246 var tag = entry.alarm_id;
6247 var icon = 'images/seo-performance-128.png';
6248 var interaction = false;
6252 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6254 switch(entry.status) {
6262 case 'UNINITIALIZED':
6266 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6267 // console.log('alarm ' + entry.unique_id + ' is not current');
6270 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6271 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6274 if(entry.no_clear_notification === true) {
6275 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6278 title = name + ' back to normal (' + value_string.toString() + ')';
6279 icon = 'images/check-mark-2-128-green.png';
6280 interaction = false;
6284 if(entry.old_status === 'CRITICAL')
6285 status = 'demoted to ' + entry.status.toLowerCase();
6287 icon = 'images/alert-128-orange.png';
6288 interaction = false;
6292 if(entry.old_status === 'WARNING')
6293 status = 'escalated to ' + entry.status.toLowerCase();
6295 icon = 'images/alert-128-red.png';
6300 console.log('invalid alarm status ' + entry.status);
6305 // cleanup old notifications with the same alarm_id as this one
6306 // FIXME: it does not seem to work on any web browser!
6307 var len = NETDATA.alarms.notifications_shown.length;
6309 var n = NETDATA.alarms.notifications_shown[len];
6310 if(n.data.alarm_id === entry.alarm_id) {
6311 console.log('removing old alarm ' + n.data.unique_id);
6313 // close the notification
6316 // remove it from the array
6317 NETDATA.alarms.notifications_shown.splice(len, 1);
6318 len = NETDATA.alarms.notifications_shown.length;
6325 setTimeout(function() {
6326 // show this notification
6327 // console.log('new notification: ' + title);
6328 var n = new Notification(title, {
6329 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6331 requireInteraction: interaction,
6332 icon: NETDATA.serverDefault + icon,
6336 n.onclick = function(event) {
6337 event.preventDefault();
6338 NETDATA.alarms.onclick(event.target.data);
6342 // NETDATA.alarms.notifications_shown.push(n);
6343 // console.log(entry);
6344 }, NETDATA.alarms.ms_penalty);
6346 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6350 scrollToChart: function(chart_id) {
6351 if(typeof chart_id === 'string') {
6352 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6353 if(typeof offset !== 'undefined') {
6354 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6361 scrollToAlarm: function(alarm) {
6362 if(typeof alarm === 'object') {
6363 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6365 if(ret === true && NETDATA.options.page_is_visible === false)
6367 // 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.');
6372 notifyAll: function() {
6373 // console.log('FETCHING ALARM LOG');
6374 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6375 // console.log('ALARM LOG FETCHED');
6377 if(data === null || typeof data !== 'object') {
6378 console.log('invalid alarms log response');
6382 if(data.length === 0) {
6383 console.log('received empty alarm log');
6387 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6389 data.sort(function(a, b) {
6390 if(a.unique_id > b.unique_id) return -1;
6391 if(a.unique_id < b.unique_id) return 1;
6395 NETDATA.alarms.ms_penalty = 0;
6397 var len = data.length;
6399 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6400 NETDATA.alarms.notify(data[len]);
6403 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6406 NETDATA.alarms.last_notification_id = data[0].unique_id;
6407 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6408 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6412 check_notifications: function() {
6413 // returns true if we should fire 1+ notifications
6415 if(NETDATA.alarms.notifications !== true) {
6416 // console.log('notifications not available');
6420 if(Notification.permission !== 'granted') {
6421 // console.log('notifications not granted');
6425 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6426 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6428 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6429 // console.log('new alarms detected');
6432 //else console.log('no new alarms');
6434 // else console.log('cannot process alarms');
6439 get: function(what, callback) {
6441 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6445 'Cache-Control': 'no-cache, no-store',
6446 'Pragma': 'no-cache'
6448 xhrFields: { withCredentials: true } // required for the cookie
6450 .done(function(data) {
6451 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6452 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6454 if(typeof callback === 'function')
6455 return callback(data);
6458 NETDATA.error(415, NETDATA.alarms.server);
6460 if(typeof callback === 'function')
6461 return callback(null);
6465 update_forever: function() {
6466 NETDATA.alarms.get('active', function(data) {
6468 NETDATA.alarms.current = data;
6470 if(NETDATA.alarms.check_notifications() === true) {
6471 NETDATA.alarms.notifyAll();
6474 if (typeof NETDATA.alarms.callback === 'function') {
6475 NETDATA.alarms.callback(data);
6478 // Health monitoring is disabled on this netdata
6479 if(data.status === false) return;
6482 setTimeout(NETDATA.alarms.update_forever, 10000);
6486 get_log: function(last_id, callback) {
6487 // console.log('fetching all log after ' + last_id.toString());
6489 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6493 'Cache-Control': 'no-cache, no-store',
6494 'Pragma': 'no-cache'
6496 xhrFields: { withCredentials: true } // required for the cookie
6498 .done(function(data) {
6499 if(typeof callback === 'function')
6500 return callback(data);
6503 NETDATA.error(416, NETDATA.alarms.server);
6505 if(typeof callback === 'function')
6506 return callback(null);
6511 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6513 NETDATA.alarms.last_notification_id =
6514 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6516 if(NETDATA.alarms.onclick === null)
6517 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6519 if(netdataShowAlarms === true) {
6520 NETDATA.alarms.update_forever();
6522 if('Notification' in window) {
6523 // console.log('notifications available');
6524 NETDATA.alarms.notifications = true;
6526 if(Notification.permission === 'default')
6527 Notification.requestPermission();
6533 // ----------------------------------------------------------------------------------------------------------------
6534 // Registry of netdata hosts
6536 NETDATA.registry = {
6537 server: null, // the netdata registry server
6538 person_guid: null, // the unique ID of this browser / user
6539 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6540 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6541 machines: null, // the user's other URLs
6542 machines_array: null, // the user's other URLs in an array
6545 parsePersonUrls: function(person_urls) {
6546 // console.log(person_urls);
6547 NETDATA.registry.person_urls = person_urls;
6550 NETDATA.registry.machines = {};
6551 NETDATA.registry.machines_array = [];
6553 var apu = person_urls;
6556 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6557 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6563 accesses: apu[i][3],
6567 obj.alternate_urls.push(apu[i][1]);
6569 NETDATA.registry.machines[apu[i][0]] = obj;
6570 NETDATA.registry.machines_array.push(obj);
6573 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6575 var pu = NETDATA.registry.machines[apu[i][0]];
6576 if(pu.last_t < apu[i][2]) {
6578 pu.last_t = apu[i][2];
6579 pu.name = apu[i][4];
6581 pu.accesses += apu[i][3];
6582 pu.alternate_urls.push(apu[i][1]);
6587 if(typeof netdataRegistryCallback === 'function')
6588 netdataRegistryCallback(NETDATA.registry.machines_array);
6592 if(netdataRegistry !== true) return;
6594 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6596 NETDATA.registry.server = data.registry;
6597 NETDATA.registry.machine_guid = data.machine_guid;
6598 NETDATA.registry.hostname = data.hostname;
6600 NETDATA.registry.access(2, function (person_urls) {
6601 NETDATA.registry.parsePersonUrls(person_urls);
6608 hello: function(host, callback) {
6609 host = NETDATA.fixHost(host);
6611 // send HELLO to a netdata server:
6612 // 1. verifies the server is reachable
6613 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6615 url: host + '/api/v1/registry?action=hello',
6619 'Cache-Control': 'no-cache, no-store',
6620 'Pragma': 'no-cache'
6622 xhrFields: { withCredentials: true } // required for the cookie
6624 .done(function(data) {
6625 if(typeof data.status !== 'string' || data.status !== 'ok') {
6626 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6630 if(typeof callback === 'function')
6631 return callback(data);
6634 NETDATA.error(407, host);
6636 if(typeof callback === 'function')
6637 return callback(null);
6641 access: function(max_redirects, callback) {
6642 // send ACCESS to a netdata registry:
6643 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6644 // 2. it responds with a list of netdata servers we know
6645 // the registry identifies us using a cookie it sets the first time we access it
6646 // the registry may respond with a redirect URL to send us to another registry
6648 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),
6652 'Cache-Control': 'no-cache, no-store',
6653 'Pragma': 'no-cache'
6655 xhrFields: { withCredentials: true } // required for the cookie
6657 .done(function(data) {
6658 var redirect = null;
6659 if(typeof data.registry === 'string')
6660 redirect = data.registry;
6662 if(typeof data.status !== 'string' || data.status !== 'ok') {
6663 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6668 if(redirect !== null && max_redirects > 0) {
6669 NETDATA.registry.server = redirect;
6670 NETDATA.registry.access(max_redirects - 1, callback);
6673 if(typeof callback === 'function')
6674 return callback(null);
6678 if(typeof data.person_guid === 'string')
6679 NETDATA.registry.person_guid = data.person_guid;
6681 if(typeof callback === 'function')
6682 return callback(data.urls);
6686 NETDATA.error(410, NETDATA.registry.server);
6688 if(typeof callback === 'function')
6689 return callback(null);
6693 delete: function(delete_url, callback) {
6694 // send DELETE to a netdata registry:
6696 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),
6700 'Cache-Control': 'no-cache, no-store',
6701 'Pragma': 'no-cache'
6703 xhrFields: { withCredentials: true } // required for the cookie
6705 .done(function(data) {
6706 if(typeof data.status !== 'string' || data.status !== 'ok') {
6707 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6711 if(typeof callback === 'function')
6712 return callback(data);
6715 NETDATA.error(412, NETDATA.registry.server);
6717 if(typeof callback === 'function')
6718 return callback(null);
6722 search: function(machine_guid, callback) {
6723 // SEARCH for the URLs of a machine:
6725 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,
6729 'Cache-Control': 'no-cache, no-store',
6730 'Pragma': 'no-cache'
6732 xhrFields: { withCredentials: true } // required for the cookie
6734 .done(function(data) {
6735 if(typeof data.status !== 'string' || data.status !== 'ok') {
6736 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6740 if(typeof callback === 'function')
6741 return callback(data);
6744 NETDATA.error(418, NETDATA.registry.server);
6746 if(typeof callback === 'function')
6747 return callback(null);
6751 switch: function(new_person_guid, callback) {
6754 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,
6758 'Cache-Control': 'no-cache, no-store',
6759 'Pragma': 'no-cache'
6761 xhrFields: { withCredentials: true } // required for the cookie
6763 .done(function(data) {
6764 if(typeof data.status !== 'string' || data.status !== 'ok') {
6765 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6769 if(typeof callback === 'function')
6770 return callback(data);
6773 NETDATA.error(414, NETDATA.registry.server);
6775 if(typeof callback === 'function')
6776 return callback(null);
6781 // ----------------------------------------------------------------------------------------------------------------
6784 if(typeof netdataPrepCallback === 'function')
6785 netdataPrepCallback();
6787 NETDATA.errorReset();
6788 NETDATA.loadRequiredCSS(0);
6790 NETDATA._loadjQuery(function() {
6791 NETDATA.loadRequiredJs(0, function() {
6792 if(typeof $().emulateTransitionEnd !== 'function') {
6793 // bootstrap is not available
6794 NETDATA.options.current.show_help = false;
6797 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6798 if(NETDATA.options.debug.main_loop === true)
6799 console.log('starting chart refresh thread');
6805 })(window, document);