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 = d;
1372 force_update_at: 0, // the timestamp to force the update at
1373 force_before_ms: null,
1374 force_after_ms: null
1379 force_update_at: 0, // the timestamp to force the update at
1380 force_before_ms: null,
1381 force_after_ms: null
1386 force_update_at: 0, // the timestamp to force the update at
1387 force_before_ms: null,
1388 force_after_ms: null
1391 // this is a pointer to one of the sub-classes below
1393 this.current = this.auto;
1395 // check the requested library is available
1396 // we don't initialize it here - it will be initialized when
1397 // this chart will be first used
1398 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1399 NETDATA.error(402, that.library_name);
1400 error('chart library "' + that.library_name + '" is not found');
1403 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1404 NETDATA.error(403, that.library_name);
1405 error('chart library "' + that.library_name + '" is not enabled');
1409 that.library = NETDATA.chartLibraries[that.library_name];
1411 // milliseconds - the time the last refresh took
1412 this.refresh_dt_ms = 0;
1414 // if we need to report the rendering speed
1415 // find the element that needs to be updated
1416 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1418 if(refresh_dt_element_name !== null) {
1419 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1422 this.refresh_dt_element = null;
1424 this.dimensions_visibility = new dimensionsVisibility(this);
1426 this._updating = false;
1428 // ============================================================================================================
1429 // PRIVATE FUNCTIONS
1431 var createDOM = function() {
1432 if(that.enabled === false) return;
1434 if(that.element_message !== null) that.element_message.innerHTML = '';
1435 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1436 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1438 that.element.innerHTML = '';
1440 that.element_message = document.createElement('div');
1441 that.element_message.className = 'netdata-message icon hidden';
1442 that.element.appendChild(that.element_message);
1444 that.element_chart = document.createElement('div');
1445 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1446 that.element.appendChild(that.element_chart);
1448 if(that.hasLegend() === true) {
1449 that.element.className = "netdata-container-with-legend";
1450 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1452 that.element_legend = document.createElement('div');
1453 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1454 that.element.appendChild(that.element_legend);
1457 that.element.className = "netdata-container";
1458 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1460 that.element_legend = null;
1462 that.element_legend_childs.series = null;
1464 if(typeof(that.width) === 'string')
1465 $(that.element).css('width', that.width);
1466 else if(typeof(that.width) === 'number')
1467 $(that.element).css('width', that.width + 'px');
1469 if(typeof(that.library.aspect_ratio) === 'undefined') {
1470 if(typeof(that.height) === 'string')
1471 that.element.style.height = that.height;
1472 else if(typeof(that.height) === 'number')
1473 that.element.style.height = that.height.toString() + 'px';
1476 var w = that.element.offsetWidth;
1477 if(w === null || w === 0) {
1478 // the div is hidden
1479 // this will resize the chart when next viewed
1480 that.tm.last_resized = 0;
1483 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1486 if(NETDATA.chartDefaults.min_width !== null)
1487 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1489 that.tm.last_dom_created = Date.now();
1495 * initialize state variables
1496 * destroy all (possibly) created state elements
1497 * create the basic DOM for a chart
1499 var init = function() {
1500 if(that.enabled === false) return;
1502 that.paused = false;
1503 that.selected = false;
1505 that.chart_created = false; // boolean - is the library.create() been called?
1506 that.updates_counter = 0; // numeric - the number of refreshes made so far
1507 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1508 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1511 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1512 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1513 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1515 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1516 last_updated: 0, // the timestamp the chart last updated with data
1517 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1519 // Used with NETDATA.globalPanAndZoom.seq
1520 last_visible_check: 0, // the time we last checked if it is visible
1521 last_resized: 0, // the time the chart was resized
1522 last_hidden: 0, // the time the chart was hidden
1523 last_unhidden: 0, // the time the chart was unhidden
1524 last_autorefreshed: 0 // the time the chart was last refreshed
1527 that.data = null; // the last data as downloaded from the netdata server
1528 that.data_url = 'invalid://'; // string - the last url used to update the chart
1529 that.data_points = 0; // number - the number of points returned from netdata
1530 that.data_after = 0; // milliseconds - the first timestamp of the data
1531 that.data_before = 0; // milliseconds - the last timestamp of the data
1532 that.data_update_every = 0; // milliseconds - the frequency to update the data
1534 that.tm.last_initialized = Date.now();
1537 that.setMode('auto');
1540 var maxMessageFontSize = function() {
1541 var screenHeight = screen.height;
1542 var el = that.element;
1544 // normally we want a font size, as tall as the element
1545 var h = el.clientHeight;
1547 // but give it some air, 20% let's say, or 5 pixels min
1548 var lost = Math.max(h * 0.2, 5);
1551 // center the text, vertically
1552 var paddingTop = (lost - 5) / 2;
1554 // but check the width too
1555 // it should fit 10 characters in it
1556 var w = el.clientWidth / 10;
1558 paddingTop += (h - w) / 2;
1562 // and don't make it too huge
1563 // 5% of the screen size is good
1564 if(h > screenHeight / 20) {
1565 paddingTop += (h - (screenHeight / 20)) / 2;
1566 h = screenHeight / 20;
1570 that.element_message.style.fontSize = h.toString() + 'px';
1571 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1574 var showMessageIcon = function(icon) {
1575 that.element_message.innerHTML = icon;
1576 maxMessageFontSize();
1577 $(that.element_message).removeClass('hidden');
1578 that.___messageHidden___ = undefined;
1581 var hideMessage = function() {
1582 if(typeof that.___messageHidden___ === 'undefined') {
1583 that.___messageHidden___ = true;
1584 $(that.element_message).addClass('hidden');
1588 var showRendering = function() {
1590 if(that.chart !== null) {
1591 if(that.chart.chart_type === 'line')
1592 icon = '<i class="fa fa-line-chart"></i>';
1594 icon = '<i class="fa fa-area-chart"></i>';
1597 icon = '<i class="fa fa-area-chart"></i>';
1599 showMessageIcon(icon + ' netdata');
1602 var showLoading = function() {
1603 if(that.chart_created === false) {
1604 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1610 var isHidden = function() {
1611 return (typeof that.___chartIsHidden___ !== 'undefined');
1614 // hide the chart, when it is not visible - called from isVisible()
1615 var hideChart = function() {
1616 // hide it, if it is not already hidden
1617 if(isHidden() === true) return;
1619 if(that.chart_created === true) {
1620 if(NETDATA.options.current.destroy_on_hide === true) {
1621 // we should destroy it
1626 that.element_chart.style.display = 'none';
1627 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1628 that.tm.last_hidden = Date.now();
1631 // This works, but I not sure there are no corner cases somewhere
1632 // so it is commented - if the user has memory issues he can
1633 // set Destroy on Hide for all charts
1634 // that.data = null;
1638 that.___chartIsHidden___ = true;
1641 // unhide the chart, when it is visible - called from isVisible()
1642 var unhideChart = function() {
1643 if(isHidden() === false) return;
1645 that.___chartIsHidden___ = undefined;
1646 that.updates_since_last_unhide = 0;
1648 if(that.chart_created === false) {
1649 // we need to re-initialize it, to show our background
1650 // logo in bootstrap tabs, until the chart loads
1654 that.tm.last_unhidden = Date.now();
1655 that.element_chart.style.display = '';
1656 if(that.element_legend !== null) that.element_legend.style.display = '';
1662 var canBeRendered = function() {
1663 return (isHidden() === false && that.isVisible(true) === true);
1666 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1667 var callChartLibraryUpdateSafely = function(data) {
1670 if(canBeRendered() === false)
1673 if(NETDATA.options.debug.chart_errors === true)
1674 status = that.library.update(that, data);
1677 status = that.library.update(that, data);
1684 if(status === false) {
1685 error('chart failed to be updated as ' + that.library_name);
1692 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1693 var callChartLibraryCreateSafely = function(data) {
1696 if(canBeRendered() === false)
1699 if(NETDATA.options.debug.chart_errors === true)
1700 status = that.library.create(that, data);
1703 status = that.library.create(that, data);
1710 if(status === false) {
1711 error('chart failed to be created as ' + that.library_name);
1715 that.chart_created = true;
1716 that.updates_since_last_creation = 0;
1720 // ----------------------------------------------------------------------------------------------------------------
1723 // resizeChart() - private
1724 // to be called just before the chart library to make sure that
1725 // a properly sized dom is available
1726 var resizeChart = function() {
1727 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1728 if(that.chart_created === false) return;
1730 if(that.needsRecreation()) {
1733 else if(typeof that.library.resize === 'function') {
1734 that.library.resize(that);
1736 if(that.element_legend_childs.perfect_scroller !== null)
1737 Ps.update(that.element_legend_childs.perfect_scroller);
1739 maxMessageFontSize();
1742 that.tm.last_resized = Date.now();
1746 // this is the actual chart resize algorithm
1748 // - resize the entire container
1749 // - update the internal states
1750 // - resize the chart as the div changes height
1751 // - update the scrollbar of the legend
1752 var resizeChartToHeight = function(h) {
1754 that.element.style.height = h;
1756 if(that.settings_id !== null)
1757 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1759 var now = Date.now();
1760 NETDATA.options.last_page_scroll = now;
1761 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1764 that.tm.last_resized = 0;
1768 this.resizeHandler = function(e) {
1771 if(typeof this.event_resize === 'undefined'
1772 || this.event_resize.chart_original_w === 'undefined'
1773 || this.event_resize.chart_original_h === 'undefined')
1774 this.event_resize = {
1775 chart_original_w: this.element.clientWidth,
1776 chart_original_h: this.element.clientHeight,
1780 if(e.type === 'touchstart') {
1781 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1782 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1785 this.event_resize.mouse_start_x = e.clientX;
1786 this.event_resize.mouse_start_y = e.clientY;
1789 this.event_resize.chart_start_w = this.element.clientWidth;
1790 this.event_resize.chart_start_h = this.element.clientHeight;
1791 this.event_resize.chart_last_w = this.element.clientWidth;
1792 this.event_resize.chart_last_h = this.element.clientHeight;
1794 var now = Date.now();
1795 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1796 // double click / double tap event
1798 // console.dir(this.element_legend_childs.content);
1799 // console.dir(this.element_legend_childs.perfect_scroller);
1801 // the optimal height of the chart
1802 // showing the entire legend
1803 var optimal = this.event_resize.chart_last_h
1804 + this.element_legend_childs.perfect_scroller.scrollHeight
1805 - this.element_legend_childs.perfect_scroller.clientHeight;
1807 // if we are not optimal, be optimal
1808 if(this.event_resize.chart_last_h !== optimal) {
1809 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1810 resizeChartToHeight(optimal.toString() + 'px');
1813 // else if the current height is not the original/saved height
1814 // reset to the original/saved height
1815 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1816 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1817 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1820 // else if the current height is not the internal default height
1821 // reset to the internal default height
1822 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1823 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1824 resizeChartToHeight(this.height_original.toString());
1827 // else if the current height is not the firstchild's clientheight
1829 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1830 var parent_rect = this.element.getBoundingClientRect();
1831 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1832 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1834 // console.log(parent_rect);
1835 // console.log(content_rect);
1836 // console.log(wanted);
1838 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1839 if(this.event_resize.chart_last_h !== wanted)
1840 resizeChartToHeight(wanted.toString() + 'px');
1844 this.event_resize.last = now;
1846 // process movement event
1847 document.onmousemove =
1848 document.ontouchmove =
1849 this.element_legend_childs.resize_handler.onmousemove =
1850 this.element_legend_childs.resize_handler.ontouchmove =
1855 case 'mousemove': y = e.clientY; break;
1856 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1860 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1862 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1863 resizeChartToHeight(newH.toString() + 'px');
1864 that.event_resize.chart_last_h = newH;
1869 // process end event
1870 document.onmouseup =
1871 document.ontouchend =
1872 this.element_legend_childs.resize_handler.onmouseup =
1873 this.element_legend_childs.resize_handler.ontouchend =
1877 // remove all the hooks
1878 document.onmouseup =
1879 document.onmousemove =
1880 document.ontouchmove =
1881 document.ontouchend =
1882 that.element_legend_childs.resize_handler.onmousemove =
1883 that.element_legend_childs.resize_handler.ontouchmove =
1884 that.element_legend_childs.resize_handler.onmouseout =
1885 that.element_legend_childs.resize_handler.onmouseup =
1886 that.element_legend_childs.resize_handler.ontouchend =
1889 // allow auto-refreshes
1890 NETDATA.options.auto_refresher_stop_until = 0;
1896 var noDataToShow = function() {
1897 showMessageIcon('<i class="fa fa-warning"></i> empty');
1898 that.legendUpdateDOM();
1899 that.tm.last_autorefreshed = Date.now();
1900 // that.data_update_every = 30 * 1000;
1901 //that.element_chart.style.display = 'none';
1902 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1903 //that.___chartIsHidden___ = true;
1906 // ============================================================================================================
1909 this.error = function(msg) {
1913 this.setMode = function(m) {
1914 if(this.current !== null && this.current.name === m) return;
1917 this.current = this.auto;
1918 else if(m === 'pan')
1919 this.current = this.pan;
1920 else if(m === 'zoom')
1921 this.current = this.zoom;
1923 this.current = this.auto;
1925 this.current.force_update_at = 0;
1926 this.current.force_before_ms = null;
1927 this.current.force_after_ms = null;
1929 this.tm.last_mode_switch = Date.now();
1932 // ----------------------------------------------------------------------------------------------------------------
1933 // global selection sync
1935 // prevent to global selection sync for some time
1936 this.globalSelectionSyncDelay = function(ms) {
1937 if(NETDATA.options.current.sync_selection === false)
1940 if(typeof ms === 'number')
1941 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1943 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1946 // can we globally apply selection sync?
1947 this.globalSelectionSyncAbility = function() {
1948 if(NETDATA.options.current.sync_selection === false)
1951 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1954 this.globalSelectionSyncIsMaster = function() {
1955 return (NETDATA.globalSelectionSync.state === this);
1958 // this chart is the master of the global selection sync
1959 this.globalSelectionSyncBeMaster = function() {
1961 if(this.globalSelectionSyncIsMaster()) {
1962 if(this.debug === true)
1963 this.log('sync: I am the master already.');
1968 if(NETDATA.globalSelectionSync.state) {
1969 if(this.debug === true)
1970 this.log('sync: I am not the sync master. Resetting global sync.');
1972 this.globalSelectionSyncStop();
1975 // become the master
1976 if(this.debug === true)
1977 this.log('sync: becoming sync master.');
1979 this.selected = true;
1980 NETDATA.globalSelectionSync.state = this;
1982 // find the all slaves
1983 var targets = NETDATA.options.targets;
1984 var len = targets.length;
1986 var st = targets[len];
1989 if(this.debug === true)
1990 st.log('sync: not adding me to sync');
1992 else if(st.globalSelectionSyncIsEligible()) {
1993 if(this.debug === true)
1994 st.log('sync: adding to sync as slave');
1996 st.globalSelectionSyncBeSlave();
2000 // this.globalSelectionSyncDelay(100);
2003 // can the chart participate to the global selection sync as a slave?
2004 this.globalSelectionSyncIsEligible = function() {
2005 return (this.enabled === true
2006 && this.library !== null
2007 && typeof this.library.setSelection === 'function'
2008 && this.isVisible() === true
2009 && this.chart_created === true);
2012 // this chart becomes a slave of the global selection sync
2013 this.globalSelectionSyncBeSlave = function() {
2014 if(NETDATA.globalSelectionSync.state !== this)
2015 NETDATA.globalSelectionSync.slaves.push(this);
2018 // sync all the visible charts to the given time
2019 // this is to be called from the chart libraries
2020 this.globalSelectionSync = function(t) {
2021 if(this.globalSelectionSyncAbility() === false)
2024 if(this.globalSelectionSyncIsMaster() === false) {
2025 if(this.debug === true)
2026 this.log('sync: trying to be sync master.');
2028 this.globalSelectionSyncBeMaster();
2030 if(this.globalSelectionSyncAbility() === false)
2034 NETDATA.globalSelectionSync.last_t = t;
2035 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2040 // stop syncing all charts to the given time
2041 this.globalSelectionSyncStop = function() {
2042 if(NETDATA.globalSelectionSync.slaves.length) {
2043 if(this.debug === true)
2044 this.log('sync: cleaning up...');
2046 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2048 if(that.debug === true)
2049 st.log('sync: not adding me to sync stop');
2052 if(that.debug === true)
2053 st.log('sync: removed slave from sync');
2055 st.clearSelection();
2059 NETDATA.globalSelectionSync.last_t = 0;
2060 NETDATA.globalSelectionSync.slaves = [];
2061 NETDATA.globalSelectionSync.state = null;
2064 this.clearSelection();
2067 this.setSelection = function(t) {
2068 if(typeof this.library.setSelection === 'function')
2069 this.selected = (this.library.setSelection(this, t) === true);
2071 this.selected = true;
2073 if(this.selected === true && this.debug === true)
2074 this.log('selection set to ' + t.toString());
2076 return this.selected;
2079 this.clearSelection = function() {
2080 if(this.selected === true) {
2081 if(typeof this.library.clearSelection === 'function')
2082 this.selected = (this.library.clearSelection(this) !== true);
2084 this.selected = false;
2086 if(this.selected === false && this.debug === true)
2087 this.log('selection cleared');
2092 return this.selected;
2095 // find if a timestamp (ms) is shown in the current chart
2096 this.timeIsVisible = function(t) {
2097 return (t >= this.data_after && t <= this.data_before);
2100 this.calculateRowForTime = function(t) {
2101 if(this.timeIsVisible(t) === false) return -1;
2102 return Math.floor((t - this.data_after) / this.data_update_every);
2105 // ----------------------------------------------------------------------------------------------------------------
2108 this.log = function(msg) {
2109 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2112 this.pauseChart = function() {
2113 if(this.paused === false) {
2114 if(this.debug === true)
2115 this.log('pauseChart()');
2121 this.unpauseChart = function() {
2122 if(this.paused === true) {
2123 if(this.debug === true)
2124 this.log('unpauseChart()');
2126 this.paused = false;
2130 this.resetChart = function(dont_clear_master, dont_update) {
2131 if(this.debug === true)
2132 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2134 if(typeof dont_clear_master === 'undefined')
2135 dont_clear_master = false;
2137 if(typeof dont_update === 'undefined')
2138 dont_update = false;
2140 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2141 if(this.debug === true)
2142 this.log('resetChart() diverting to clearMaster().');
2143 // this will call us back with master === true
2144 NETDATA.globalPanAndZoom.clearMaster();
2148 this.clearSelection();
2150 this.tm.pan_and_zoom_seq = 0;
2152 this.setMode('auto');
2153 this.current.force_update_at = 0;
2154 this.current.force_before_ms = null;
2155 this.current.force_after_ms = null;
2156 this.tm.last_autorefreshed = 0;
2157 this.paused = false;
2158 this.selected = false;
2159 this.enabled = true;
2160 // this.debug = false;
2162 // do not update the chart here
2163 // or the chart will flip-flop when it is the master
2164 // of a selection sync and another chart becomes
2167 if(dont_update !== true && this.isVisible() === true) {
2172 this.updateChartPanOrZoom = function(after, before) {
2173 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2176 if(this.debug === true)
2179 if(before < after) {
2180 if(this.debug === true)
2181 this.log(logme + 'flipped parameters, rejecting it.');
2186 if(typeof this.fixed_min_duration === 'undefined')
2187 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2189 var min_duration = this.fixed_min_duration;
2190 var current_duration = Math.round(this.view_before - this.view_after);
2192 // round the numbers
2193 after = Math.round(after);
2194 before = Math.round(before);
2196 // align them to update_every
2197 // stretching them further away
2198 after -= after % this.data_update_every;
2199 before += this.data_update_every - (before % this.data_update_every);
2201 // the final wanted duration
2202 var wanted_duration = before - after;
2204 // to allow panning, accept just a point below our minimum
2205 if((current_duration - this.data_update_every) < min_duration)
2206 min_duration = current_duration - this.data_update_every;
2208 // we do it, but we adjust to minimum size and return false
2209 // when the wanted size is below the current and the minimum
2211 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2212 if(this.debug === true)
2213 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2215 min_duration = this.fixed_min_duration;
2217 var dt = (min_duration - wanted_duration) / 2;
2220 wanted_duration = before - after;
2224 var tolerance = this.data_update_every * 2;
2225 var movement = Math.abs(before - this.view_before);
2227 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2228 if(this.debug === true)
2229 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2233 if(this.current.name === 'auto') {
2234 this.log(logme + 'caller called me with mode: ' + this.current.name);
2235 this.setMode('pan');
2238 if(this.debug === true)
2239 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2241 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2242 this.current.force_after_ms = after;
2243 this.current.force_before_ms = before;
2244 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2248 var __legendFormatValueChartDecimalsLastMin = undefined;
2249 var __legendFormatValueChartDecimalsLastMax = undefined;
2250 var __legendFormatValueChartDecimals = -1;
2251 this.legendFormatValueDecimalsFromMinMax = function(min, max) {
2252 if(min === __legendFormatValueChartDecimalsLastMin && max === __legendFormatValueChartDecimalsLastMax)
2255 if(this.value_decimal_detail !== -1) {
2256 __legendFormatValueChartDecimals = this.value_decimal_detail;
2259 __legendFormatValueChartDecimalsLastMin = min;
2260 __legendFormatValueChartDecimalsLastMax = max;
2265 delta = Math.abs(min);
2267 delta = Math.abs(max - min);
2269 if (delta > 1000) __legendFormatValueChartDecimals = 0;
2270 else if (delta > 10) __legendFormatValueChartDecimals = 1;
2271 else if (delta > 1) __legendFormatValueChartDecimals = 2;
2272 else if (delta > 0.1) __legendFormatValueChartDecimals = 3;
2273 else __legendFormatValueChartDecimals = 4;
2277 this.legendFormatValue = function(value) {
2278 if(typeof value !== 'number') return '-';
2282 if(__legendFormatValueChartDecimals < 0) {
2285 if(abs > 1000) dmax = 0;
2286 else if(abs > 10 ) dmax = 1;
2287 else if(abs > 1) dmax = 2;
2288 else if(abs > 0.1) dmax = 3;
2292 dmin = dmax = __legendFormatValueChartDecimals;
2295 if(this.value_decimal_detail !== -1) {
2296 dmin = dmax = this.value_decimal_detail;
2299 return value.toLocaleString(undefined, {
2300 // style: 'decimal',
2301 // minimumIntegerDigits: 1,
2302 // minimumSignificantDigits: 1,
2303 // maximumSignificantDigits: 1,
2305 minimumFractionDigits: dmin,
2306 maximumFractionDigits: dmax
2310 this.legendSetLabelValue = function(label, value) {
2311 var series = this.element_legend_childs.series[label];
2312 if(typeof series === 'undefined') return;
2313 if(series.value === null && series.user === null) return;
2316 // this slows down firefox and edge significantly
2317 // since it requires to use innerHTML(), instead of innerText()
2319 // if the value has not changed, skip DOM update
2320 //if(series.last === value) return;
2323 if(typeof value === 'number') {
2324 var v = Math.abs(value);
2325 s = r = this.legendFormatValue(value);
2327 if(typeof series.last === 'number') {
2328 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2329 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2330 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2332 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2342 series.last = value;
2346 var s = this.legendFormatValue(value);
2348 // caching: do not update the update to show the same value again
2349 if(s === series.last_shown_value) return;
2350 series.last_shown_value = s;
2352 if(series.value !== null) series.value.innerText = s;
2353 if(series.user !== null) series.user.innerText = s;
2356 this.__legendSetDateString = function(date) {
2357 if(date !== this.__last_shown_legend_date) {
2358 this.element_legend_childs.title_date.innerText = date;
2359 this.__last_shown_legend_date = date;
2363 this.__legendSetTimeString = function(time) {
2364 if(time !== this.__last_shown_legend_time) {
2365 this.element_legend_childs.title_time.innerText = time;
2366 this.__last_shown_legend_time = time;
2370 this.__legendSetUnitsString = function(units) {
2371 if(units !== this.__last_shown_legend_units) {
2372 this.element_legend_childs.title_units.innerText = units;
2373 this.__last_shown_legend_units = units;
2377 this.legendSetDateLast = {
2383 this.legendSetDate = function(ms) {
2384 if(typeof ms !== 'number') {
2385 this.legendShowUndefined();
2389 if(this.legendSetDateLast.ms !== ms) {
2390 var d = new Date(ms);
2391 this.legendSetDateLast.ms = ms;
2392 this.legendSetDateLast.date = d.toLocaleDateString();
2393 this.legendSetDateLast.time = d.toLocaleTimeString();
2396 if(this.element_legend_childs.title_date !== null)
2397 this.__legendSetDateString(this.legendSetDateLast.date);
2399 if(this.element_legend_childs.title_time !== null)
2400 this.__legendSetTimeString(this.legendSetDateLast.time);
2402 if(this.element_legend_childs.title_units !== null)
2403 this.__legendSetUnitsString(this.units)
2406 this.legendShowUndefined = function() {
2407 if(this.element_legend_childs.title_date !== null)
2408 this.__legendSetDateString(' ');
2410 if(this.element_legend_childs.title_time !== null)
2411 this.__legendSetTimeString(this.chart.name);
2413 if(this.element_legend_childs.title_units !== null)
2414 this.__legendSetUnitsString(' ');
2416 if(this.data && this.element_legend_childs.series !== null) {
2417 var labels = this.data.dimension_names;
2418 var i = labels.length;
2420 var label = labels[i];
2422 if(typeof label === 'undefined' || typeof this.element_legend_childs.series[label] === 'undefined') continue;
2423 this.legendSetLabelValue(label, null);
2428 this.legendShowLatestValues = function() {
2429 if(this.chart === null) return;
2430 if(this.selected) return;
2432 if(this.data === null || this.element_legend_childs.series === null) {
2433 this.legendShowUndefined();
2437 var show_undefined = true;
2438 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2439 show_undefined = false;
2441 if(show_undefined) {
2442 this.legendShowUndefined();
2446 this.legendSetDate(this.view_before);
2448 var labels = this.data.dimension_names;
2449 var i = labels.length;
2451 var label = labels[i];
2453 if(typeof label === 'undefined') continue;
2454 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2457 this.legendSetLabelValue(label, null);
2459 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2463 this.legendReset = function() {
2464 this.legendShowLatestValues();
2467 // this should be called just ONCE per dimension per chart
2468 this._chartDimensionColor = function(label) {
2469 if(this.colors === null) this.chartColors();
2471 if(typeof this.colors_assigned[label] === 'undefined') {
2472 if(this.colors_available.length === 0) {
2473 var len = NETDATA.themes.current.colors.length;
2475 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2478 this.colors_assigned[label] = this.colors_available.shift();
2480 if(this.debug === true)
2481 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2484 if(this.debug === true)
2485 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2488 this.colors.push(this.colors_assigned[label]);
2489 return this.colors_assigned[label];
2492 this.chartColors = function() {
2493 if(this.colors !== null) return this.colors;
2496 this.colors_available = [];
2498 // add the standard colors
2499 var len = NETDATA.themes.current.colors.length;
2501 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2503 // add the user supplied colors
2504 var c = $(this.element).data('colors');
2505 // this.log('read colors: ' + c);
2506 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2507 if(typeof c !== 'string') {
2508 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2518 this.colors_available.unshift(c[len]);
2519 // this.log('adding color: ' + c[len]);
2528 this.legendUpdateDOM = function() {
2529 var needed = false, dim, keys, len, i;
2531 // check that the legend DOM is up to date for the downloaded dimensions
2532 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2533 // this.log('the legend does not have any series - requesting legend update');
2536 else if(this.data === null) {
2537 // this.log('the chart does not have any data - requesting legend update');
2540 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2544 var labels = this.data.dimension_names.toString();
2545 if(labels !== this.element_legend_childs.series.labels_key) {
2548 if(this.debug === true)
2549 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2553 if(needed === false) {
2554 // make sure colors available
2557 // do we have to update the current values?
2558 // we do this, only when the visible chart is current
2559 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2560 if(this.debug === true)
2561 this.log('chart is in latest position... updating values on legend...');
2563 //var labels = this.data.dimension_names;
2564 //var i = labels.length;
2566 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2570 if(this.colors === null) {
2571 // this is the first time we update the chart
2572 // let's assign colors to all dimensions
2573 if(this.library.track_colors() === true) {
2574 keys = Object.keys(this.chart.dimensions);
2576 for(i = 0; i < len ;i++)
2577 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2580 // we will re-generate the colors for the chart
2581 // based on the selected dimensions
2584 if(this.debug === true)
2585 this.log('updating Legend DOM');
2587 // mark all dimensions as invalid
2588 this.dimensions_visibility.invalidateAll();
2590 var genLabel = function(state, parent, dim, name, count) {
2591 var color = state._chartDimensionColor(name);
2593 var user_element = null;
2594 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2595 if(user_id === null)
2596 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2597 if(user_id !== null) {
2598 user_element = document.getElementById(user_id) || null;
2599 if (user_element === null)
2600 state.log('Cannot find element with id: ' + user_id);
2603 state.element_legend_childs.series[name] = {
2604 name: document.createElement('span'),
2605 value: document.createElement('span'),
2608 last_shown_value: null
2611 var label = state.element_legend_childs.series[name];
2613 // create the dimension visibility tracking for this label
2614 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2616 var rgb = NETDATA.colorHex2Rgb(color);
2617 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2618 + state.chart.chart_type
2619 + '" style="background-color: '
2620 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2621 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2623 var text = document.createTextNode(' ' + name);
2624 label.name.appendChild(text);
2627 parent.appendChild(document.createElement('br'));
2629 parent.appendChild(label.name);
2630 parent.appendChild(label.value);
2633 var content = document.createElement('div');
2635 if(this.hasLegend()) {
2636 this.element_legend_childs = {
2638 resize_handler: document.createElement('div'),
2639 toolbox: document.createElement('div'),
2640 toolbox_left: document.createElement('div'),
2641 toolbox_right: document.createElement('div'),
2642 toolbox_reset: document.createElement('div'),
2643 toolbox_zoomin: document.createElement('div'),
2644 toolbox_zoomout: document.createElement('div'),
2645 toolbox_volume: document.createElement('div'),
2646 title_date: document.createElement('span'),
2647 title_time: document.createElement('span'),
2648 title_units: document.createElement('span'),
2649 perfect_scroller: document.createElement('div'),
2653 this.element_legend.innerHTML = '';
2655 if(this.library.toolboxPanAndZoom !== null) {
2657 var get_pan_and_zoom_step = function(event) {
2659 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2661 else if (event.shiftKey)
2662 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2664 else if (event.altKey)
2665 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2668 return NETDATA.options.current.pan_and_zoom_factor;
2671 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2672 this.element.appendChild(this.element_legend_childs.toolbox);
2674 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2675 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2676 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2677 this.element_legend_childs.toolbox_left.onclick = function(e) {
2680 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2681 var before = that.view_before - step;
2682 var after = that.view_after - step;
2683 if(after >= that.netdata_first)
2684 that.library.toolboxPanAndZoom(that, after, before);
2686 if(NETDATA.options.current.show_help === true)
2687 $(this.element_legend_childs.toolbox_left).popover({
2692 placement: 'bottom',
2693 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2695 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>'
2699 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2700 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2701 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2702 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2704 NETDATA.resetAllCharts(that);
2706 if(NETDATA.options.current.show_help === true)
2707 $(this.element_legend_childs.toolbox_reset).popover({
2712 placement: 'bottom',
2713 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2714 title: 'Chart Reset',
2715 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>'
2718 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2719 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2720 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2721 this.element_legend_childs.toolbox_right.onclick = function(e) {
2723 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2724 var before = that.view_before + step;
2725 var after = that.view_after + step;
2726 if(before <= that.netdata_last)
2727 that.library.toolboxPanAndZoom(that, after, before);
2729 if(NETDATA.options.current.show_help === true)
2730 $(this.element_legend_childs.toolbox_right).popover({
2735 placement: 'bottom',
2736 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2738 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>'
2742 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2743 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2744 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2745 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2747 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2748 var before = that.view_before - dt;
2749 var after = that.view_after + dt;
2750 that.library.toolboxPanAndZoom(that, after, before);
2752 if(NETDATA.options.current.show_help === true)
2753 $(this.element_legend_childs.toolbox_zoomin).popover({
2758 placement: 'bottom',
2759 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2760 title: 'Chart Zoom In',
2761 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>'
2764 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2765 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2766 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2767 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2769 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);
2770 var before = that.view_before + dt;
2771 var after = that.view_after - dt;
2773 that.library.toolboxPanAndZoom(that, after, before);
2775 if(NETDATA.options.current.show_help === true)
2776 $(this.element_legend_childs.toolbox_zoomout).popover({
2781 placement: 'bottom',
2782 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2783 title: 'Chart Zoom Out',
2784 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>'
2787 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2788 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2789 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2790 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2791 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2792 //e.preventDefault();
2793 //alert('clicked toolbox_volume on ' + that.id);
2797 this.element_legend_childs.toolbox = null;
2798 this.element_legend_childs.toolbox_left = null;
2799 this.element_legend_childs.toolbox_reset = null;
2800 this.element_legend_childs.toolbox_right = null;
2801 this.element_legend_childs.toolbox_zoomin = null;
2802 this.element_legend_childs.toolbox_zoomout = null;
2803 this.element_legend_childs.toolbox_volume = null;
2806 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2807 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2808 this.element.appendChild(this.element_legend_childs.resize_handler);
2809 if(NETDATA.options.current.show_help === true)
2810 $(this.element_legend_childs.resize_handler).popover({
2815 placement: 'bottom',
2816 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2817 title: 'Chart Resize',
2818 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>'
2822 this.element_legend_childs.resize_handler.onmousedown =
2824 that.resizeHandler(e);
2828 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2829 that.resizeHandler(e);
2832 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2833 this.element_legend.appendChild(this.element_legend_childs.title_date);
2834 this.__last_shown_legend_date = undefined;
2836 this.element_legend.appendChild(document.createElement('br'));
2838 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2839 this.element_legend.appendChild(this.element_legend_childs.title_time);
2840 this.__last_shown_legend_time = undefined;
2842 this.element_legend.appendChild(document.createElement('br'));
2844 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2845 this.element_legend.appendChild(this.element_legend_childs.title_units);
2846 this.__last_shown_legend_units = undefined;
2848 this.element_legend.appendChild(document.createElement('br'));
2850 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2851 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2853 content.className = 'netdata-legend-series-content';
2854 this.element_legend_childs.perfect_scroller.appendChild(content);
2856 if(NETDATA.options.current.show_help === true)
2857 $(content).popover({
2862 placement: 'bottom',
2863 title: 'Chart Legend',
2864 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2865 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>'
2869 this.element_legend_childs = {
2871 resize_handler: null,
2874 toolbox_right: null,
2875 toolbox_reset: null,
2876 toolbox_zoomin: null,
2877 toolbox_zoomout: null,
2878 toolbox_volume: null,
2882 perfect_scroller: null,
2888 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2889 if(this.debug === true)
2890 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2892 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2893 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2898 keys = Object.keys(this.chart.dimensions);
2899 for(i = 0, len = keys.length; i < len ;i++) {
2901 tmp.push(this.chart.dimensions[dim].name);
2902 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2904 this.element_legend_childs.series.labels_key = tmp.toString();
2905 if(this.debug === true)
2906 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2909 // create a hidden div to be used for hidding
2910 // the original legend of the chart library
2911 var el = document.createElement('div');
2912 if(this.element_legend !== null)
2913 this.element_legend.appendChild(el);
2914 el.style.display = 'none';
2916 this.element_legend_childs.hidden = document.createElement('div');
2917 el.appendChild(this.element_legend_childs.hidden);
2919 if(this.element_legend_childs.perfect_scroller !== null) {
2920 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2922 wheelPropagation: true,
2923 swipePropagation: true,
2924 minScrollbarLength: null,
2925 maxScrollbarLength: null,
2926 useBothWheelAxes: false,
2927 suppressScrollX: true,
2928 suppressScrollY: false,
2929 scrollXMarginOffset: 0,
2930 scrollYMarginOffset: 0,
2933 Ps.update(this.element_legend_childs.perfect_scroller);
2936 this.legendShowLatestValues();
2939 this.hasLegend = function() {
2940 if(typeof this.___hasLegendCache___ !== 'undefined')
2941 return this.___hasLegendCache___;
2944 if(this.library && this.library.legend(this) === 'right-side') {
2945 var legend = $(this.element).data('legend') || 'yes';
2946 if(legend === 'yes') leg = true;
2949 this.___hasLegendCache___ = leg;
2953 this.legendWidth = function() {
2954 return (this.hasLegend())?140:0;
2957 this.legendHeight = function() {
2958 return $(this.element).height();
2961 this.chartWidth = function() {
2962 return $(this.element).width() - this.legendWidth();
2965 this.chartHeight = function() {
2966 return $(this.element).height();
2969 this.chartPixelsPerPoint = function() {
2970 // force an options provided detail
2971 var px = this.pixels_per_point;
2973 if(this.library && px < this.library.pixels_per_point(this))
2974 px = this.library.pixels_per_point(this);
2976 if(px < NETDATA.options.current.pixels_per_point)
2977 px = NETDATA.options.current.pixels_per_point;
2982 this.needsRecreation = function() {
2984 this.chart_created === true
2986 && this.library.autoresize() === false
2987 && this.tm.last_resized < NETDATA.options.last_resized
2991 this.chartURL = function() {
2992 var after, before, points_multiplier = 1;
2993 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2994 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2996 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2997 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2998 this.view_after = after * 1000;
2999 this.view_before = before * 1000;
3001 this.requested_padding = null;
3002 points_multiplier = 1;
3004 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
3005 this.tm.pan_and_zoom_seq = 0;
3007 before = Math.round(this.current.force_before_ms / 1000);
3008 after = Math.round(this.current.force_after_ms / 1000);
3009 this.view_after = after * 1000;
3010 this.view_before = before * 1000;
3012 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
3013 this.requested_padding = Math.round((before - after) / 2);
3014 after -= this.requested_padding;
3015 before += this.requested_padding;
3016 this.requested_padding *= 1000;
3017 points_multiplier = 2;
3020 this.current.force_before_ms = null;
3021 this.current.force_after_ms = null;
3024 this.tm.pan_and_zoom_seq = 0;
3026 before = this.before;
3028 this.view_after = after * 1000;
3029 this.view_before = before * 1000;
3031 this.requested_padding = null;
3032 points_multiplier = 1;
3035 this.requested_after = after * 1000;
3036 this.requested_before = before * 1000;
3038 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3040 // build the data URL
3041 this.data_url = this.host + this.chart.data_url;
3042 this.data_url += "&format=" + this.library.format();
3043 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
3044 this.data_url += "&group=" + this.method;
3046 if(this.override_options !== null)
3047 this.data_url += "&options=" + this.override_options.toString();
3049 this.data_url += "&options=" + this.library.options(this);
3051 this.data_url += '|jsonwrap';
3053 if(NETDATA.options.current.eliminate_zero_dimensions === true)
3054 this.data_url += '|nonzero';
3056 if(this.append_options !== null)
3057 this.data_url += '|' + this.append_options.toString();
3060 this.data_url += "&after=" + after.toString();
3063 this.data_url += "&before=" + before.toString();
3066 this.data_url += "&dimensions=" + this.dimensions;
3068 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
3069 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
3072 this.redrawChart = function() {
3073 if(this.data !== null)
3074 this.updateChartWithData(this.data);
3077 this.updateChartWithData = function(data) {
3078 if(this.debug === true)
3079 this.log('updateChartWithData() called.');
3081 // this may force the chart to be re-created
3085 this.updates_counter++;
3086 this.updates_since_last_unhide++;
3087 this.updates_since_last_creation++;
3089 var started = Date.now();
3091 // if the result is JSON, find the latest update-every
3092 this.data_update_every = data.view_update_every * 1000;
3093 this.data_after = data.after * 1000;
3094 this.data_before = data.before * 1000;
3095 this.netdata_first = data.first_entry * 1000;
3096 this.netdata_last = data.last_entry * 1000;
3097 this.data_points = data.points;
3100 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3101 if(this.view_after < this.data_after) {
3102 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3103 this.view_after = this.data_after;
3106 if(this.view_before > this.data_before) {
3107 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3108 this.view_before = this.data_before;
3112 this.view_after = this.data_after;
3113 this.view_before = this.data_before;
3116 if(this.debug === true) {
3117 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3119 if(this.current.force_after_ms)
3120 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3122 this.log('STATUS: forced : unset');
3124 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3125 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3126 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3127 this.log('STATUS: points : ' + (this.data_points).toString());
3130 if(this.data_points === 0) {
3135 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3136 if(this.debug === true)
3137 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3139 this.chart_created = false;
3142 // check and update the legend
3143 this.legendUpdateDOM();
3145 if(this.chart_created === true
3146 && typeof this.library.update === 'function') {
3148 if(this.debug === true)
3149 this.log('updating chart...');
3151 if(callChartLibraryUpdateSafely(data) === false)
3155 if(this.debug === true)
3156 this.log('creating chart...');
3158 if(callChartLibraryCreateSafely(data) === false)
3162 this.legendShowLatestValues();
3163 if(this.selected === true)
3164 NETDATA.globalSelectionSync.stop();
3166 // update the performance counters
3167 var now = Date.now();
3168 this.tm.last_updated = now;
3170 // don't update last_autorefreshed if this chart is
3171 // forced to be updated with global PanAndZoom
3172 if(NETDATA.globalPanAndZoom.isActive())
3173 this.tm.last_autorefreshed = 0;
3175 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3176 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3178 this.tm.last_autorefreshed = now;
3181 this.refresh_dt_ms = now - started;
3182 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3184 if(this.refresh_dt_element !== null)
3185 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3188 this.updateChart = function(callback) {
3189 if(this.debug === true)
3190 this.log('updateChart() called.');
3192 if(this._updating === true) {
3193 if(this.debug === true)
3194 this.log('I am already updating...');
3196 if(typeof callback === 'function')
3202 // due to late initialization of charts and libraries
3203 // we need to check this too
3204 if(this.enabled === false) {
3205 if(this.debug === true)
3206 this.log('I am not enabled');
3208 if(typeof callback === 'function')
3214 if(canBeRendered() === false) {
3215 if(typeof callback === 'function')
3221 if(this.chart === null)
3222 return this.getChart(function() {
3223 return that.updateChart(callback);
3226 if(this.library.initialized === false) {
3227 if(this.library.enabled === true) {
3228 return this.library.initialize(function () {
3229 return that.updateChart(callback);
3233 error('chart library "' + this.library_name + '" is not available.');
3235 if(typeof callback === 'function')
3242 this.clearSelection();
3245 if(this.debug === true)
3246 this.log('updating from ' + this.data_url);
3248 NETDATA.statistics.refreshes_total++;
3249 NETDATA.statistics.refreshes_active++;
3251 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3252 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3254 this._updating = true;
3256 this.xhr = $.ajax( {
3261 'Cache-Control': 'no-cache, no-store',
3262 'Pragma': 'no-cache'
3264 xhrFields: { withCredentials: true } // required for the cookie
3266 .done(function(data) {
3267 that.xhr = undefined;
3268 that.retries_on_data_failures = 0;
3270 if(that.debug === true)
3271 that.log('data received. updating chart.');
3273 that.updateChartWithData(data);
3275 .fail(function(msg) {
3276 that.xhr = undefined;
3278 if(msg.statusText !== 'abort') {
3279 that.retries_on_data_failures++;
3280 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3281 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3282 that.retries_on_data_failures = 0;
3283 error('data download failed for url: ' + that.data_url);
3286 that.tm.last_autorefreshed = Date.now();
3287 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3291 .always(function() {
3292 that.xhr = undefined;
3294 NETDATA.statistics.refreshes_active--;
3295 that._updating = false;
3297 if(typeof callback === 'function')
3302 this.isVisible = function(nocache) {
3303 if(typeof nocache === 'undefined')
3306 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3308 // caching - we do not evaluate the charts visibility
3309 // if the page has not been scrolled since the last check
3310 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3311 return this.___isVisible___;
3313 this.tm.last_visible_check = Date.now();
3315 var wh = window.innerHeight;
3316 var x = this.element.getBoundingClientRect();
3320 if(x.width === 0 || x.height === 0) {
3322 this.___isVisible___ = false;
3323 return this.___isVisible___;
3326 if(x.top < 0 && -x.top > x.height) {
3327 // the chart is entirely above
3328 ret = -x.top - x.height;
3330 else if(x.top > wh) {
3331 // the chart is entirely below
3335 if(ret > tolerance) {
3336 // the chart is too far
3339 this.___isVisible___ = false;
3340 return this.___isVisible___;
3343 // the chart is inside or very close
3346 this.___isVisible___ = true;
3347 return this.___isVisible___;
3351 this.isAutoRefreshable = function() {
3352 return (this.current.autorefresh);
3355 this.canBeAutoRefreshed = function() {
3356 var now = Date.now();
3358 if(this.running === true) {
3359 if(this.debug === true)
3360 this.log('I am already running');
3365 if(this.enabled === false) {
3366 if(this.debug === true)
3367 this.log('I am not enabled');
3372 if(this.library === null || this.library.enabled === false) {
3373 error('charting library "' + this.library_name + '" is not available');
3374 if(this.debug === true)
3375 this.log('My chart library ' + this.library_name + ' is not available');
3380 if(this.isVisible() === false) {
3381 if(NETDATA.options.debug.visibility === true || this.debug === true)
3382 this.log('I am not visible');
3387 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3388 if(this.debug === true)
3389 this.log('timed force update detected - allowing this update');
3391 this.current.force_update_at = 0;
3395 if(this.isAutoRefreshable() === true) {
3396 // allow the first update, even if the page is not visible
3397 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3398 if(NETDATA.options.debug.focus === true || this.debug === true)
3399 this.log('canBeAutoRefreshed(): page does not have focus');
3404 if(this.needsRecreation() === true) {
3405 if(this.debug === true)
3406 this.log('canBeAutoRefreshed(): needs re-creation.');
3411 // options valid only for autoRefresh()
3412 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3413 if(NETDATA.globalPanAndZoom.isActive()) {
3414 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3415 if(this.debug === true)
3416 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3421 if(this.debug === true)
3422 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3428 if(this.selected === true) {
3429 if(this.debug === true)
3430 this.log('canBeAutoRefreshed(): I have a selection in place.');
3435 if(this.paused === true) {
3436 if(this.debug === true)
3437 this.log('canBeAutoRefreshed(): I am paused.');
3442 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3443 if(this.debug === true)
3444 this.log('canBeAutoRefreshed(): It is time to update me.');
3454 this.autoRefresh = function(callback) {
3455 if(this.canBeAutoRefreshed() === true && this.running === false) {
3458 state.running = true;
3459 state.updateChart(function() {
3460 state.running = false;
3462 if(typeof callback !== 'undefined')
3467 if(typeof callback !== 'undefined')
3472 this._defaultsFromDownloadedChart = function(chart) {
3474 this.chart_url = chart.url;
3475 this.data_update_every = chart.update_every * 1000;
3476 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3477 this.tm.last_info_downloaded = Date.now();
3479 if(this.title === null)
3480 this.title = chart.title;
3482 if(this.units === null)
3483 this.units = chart.units;
3486 // fetch the chart description from the netdata server
3487 this.getChart = function(callback) {
3488 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3490 this._defaultsFromDownloadedChart(this.chart);
3492 if(typeof callback === 'function')
3496 this.chart_url = "/api/v1/chart?chart=" + this.id;
3498 if(this.debug === true)
3499 this.log('downloading ' + this.chart_url);
3502 url: this.host + this.chart_url,
3505 xhrFields: { withCredentials: true } // required for the cookie
3507 .done(function(chart) {
3508 chart.url = that.chart_url;
3509 that._defaultsFromDownloadedChart(chart);
3510 NETDATA.chartRegistry.add(that.host, that.id, chart);
3513 NETDATA.error(404, that.chart_url);
3514 error('chart not found on url "' + that.chart_url + '"');
3516 .always(function() {
3517 if(typeof callback === 'function')
3523 // ============================================================================================================
3529 NETDATA.resetAllCharts = function(state) {
3530 // first clear the global selection sync
3531 // to make sure no chart is in selected state
3532 state.globalSelectionSyncStop();
3534 // there are 2 possibilities here
3535 // a. state is the global Pan and Zoom master
3536 // b. state is not the global Pan and Zoom master
3538 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3541 // clear the global Pan and Zoom
3542 // this will also refresh the master
3543 // and unblock any charts currently mirroring the master
3544 NETDATA.globalPanAndZoom.clearMaster();
3546 // if we were not the master, reset our status too
3547 // this is required because most probably the mouse
3548 // is over this chart, blocking it from auto-refreshing
3549 if(master === false && (state.paused === true || state.selected === true))
3553 // get or create a chart state, given a DOM element
3554 NETDATA.chartState = function(element) {
3555 var state = $(element).data('netdata-state-object') || null;
3556 if(state === null) {
3557 state = new chartState(element);
3558 $(element).data('netdata-state-object', state);
3563 // ----------------------------------------------------------------------------------------------------------------
3564 // Library functions
3566 // Load a script without jquery
3567 // This is used to load jquery - after it is loaded, we use jquery
3568 NETDATA._loadjQuery = function(callback) {
3569 if(typeof jQuery === 'undefined') {
3570 if(NETDATA.options.debug.main_loop === true)
3571 console.log('loading ' + NETDATA.jQuery);
3573 var script = document.createElement('script');
3574 script.type = 'text/javascript';
3575 script.async = true;
3576 script.src = NETDATA.jQuery;
3578 // script.onabort = onError;
3579 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3580 if(typeof callback === "function")
3581 script.onload = callback;
3583 var s = document.getElementsByTagName('script')[0];
3584 s.parentNode.insertBefore(script, s);
3586 else if(typeof callback === "function")
3590 NETDATA._loadCSS = function(filename) {
3591 // don't use jQuery here
3592 // styles are loaded before jQuery
3593 // to eliminate showing an unstyled page to the user
3595 var fileref = document.createElement("link");
3596 fileref.setAttribute("rel", "stylesheet");
3597 fileref.setAttribute("type", "text/css");
3598 fileref.setAttribute("href", filename);
3600 if (typeof fileref !== 'undefined')
3601 document.getElementsByTagName("head")[0].appendChild(fileref);
3604 NETDATA.colorHex2Rgb = function(hex) {
3605 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3606 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3607 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3608 return r + r + g + g + b + b;
3611 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3613 r: parseInt(result[1], 16),
3614 g: parseInt(result[2], 16),
3615 b: parseInt(result[3], 16)
3619 NETDATA.colorLuminance = function(hex, lum) {
3620 // validate hex string
3621 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3623 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3627 // convert to decimal and change luminosity
3628 var rgb = "#", c, i;
3629 for (i = 0; i < 3; i++) {
3630 c = parseInt(hex.substr(i*2,2), 16);
3631 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3632 rgb += ("00"+c).substr(c.length);
3638 NETDATA.guid = function() {
3640 return Math.floor((1 + Math.random()) * 0x10000)
3645 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3648 NETDATA.zeropad = function(x) {
3649 if(x > -10 && x < 10) return '0' + x.toString();
3650 else return x.toString();
3653 // user function to signal us the DOM has been
3655 NETDATA.updatedDom = function() {
3656 NETDATA.options.updated_dom = true;
3659 NETDATA.ready = function(callback) {
3660 NETDATA.options.pauseCallback = callback;
3663 NETDATA.pause = function(callback) {
3664 if(typeof callback === 'function') {
3665 if (NETDATA.options.pause === true)
3668 NETDATA.options.pauseCallback = callback;
3672 NETDATA.unpause = function() {
3673 NETDATA.options.pauseCallback = null;
3674 NETDATA.options.updated_dom = true;
3675 NETDATA.options.pause = false;
3678 // ----------------------------------------------------------------------------------------------------------------
3680 // this is purely sequential charts refresher
3681 // it is meant to be autonomous
3682 NETDATA.chartRefresherNoParallel = function(index) {
3683 if(NETDATA.options.debug.main_loop === true)
3684 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3686 if(NETDATA.options.updated_dom === true) {
3687 // the dom has been updated
3688 // get the dom parts again
3689 NETDATA.parseDom(NETDATA.chartRefresher);
3692 if(index >= NETDATA.options.targets.length) {
3693 if(NETDATA.options.debug.main_loop === true)
3694 console.log('waiting to restart main loop...');
3696 NETDATA.options.auto_refresher_fast_weight = 0;
3698 setTimeout(function() {
3699 NETDATA.chartRefresher();
3700 }, NETDATA.options.current.idle_between_loops);
3703 var state = NETDATA.options.targets[index];
3705 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3706 if(NETDATA.options.debug.main_loop === true)
3707 console.log('fast rendering...');
3709 state.autoRefresh(function() {
3710 NETDATA.chartRefresherNoParallel(++index);
3714 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3715 NETDATA.options.auto_refresher_fast_weight = 0;
3717 setTimeout(function() {
3718 state.autoRefresh(function() {
3719 NETDATA.chartRefresherNoParallel(++index);
3721 }, NETDATA.options.current.idle_between_charts);
3726 NETDATA.chartRefresherWaitTime = function() {
3727 return NETDATA.options.current.idle_parallel_loops;
3730 // the default refresher
3731 NETDATA.chartRefresher = function() {
3732 // console.log('auto-refresher...');
3734 if(NETDATA.options.pause === true) {
3735 // console.log('auto-refresher is paused');
3736 setTimeout(NETDATA.chartRefresher,
3737 NETDATA.chartRefresherWaitTime());
3741 if(typeof NETDATA.options.pauseCallback === 'function') {
3742 // console.log('auto-refresher is calling pauseCallback');
3743 NETDATA.options.pause = true;
3744 NETDATA.options.pauseCallback();
3745 NETDATA.chartRefresher();
3749 if(NETDATA.options.current.parallel_refresher === false) {
3750 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3751 NETDATA.chartRefresherNoParallel(0);
3755 if(NETDATA.options.updated_dom === true) {
3756 // the dom has been updated
3757 // get the dom parts again
3758 // console.log('auto-refresher is calling parseDom()');
3759 NETDATA.parseDom(NETDATA.chartRefresher);
3764 var targets = NETDATA.options.targets;
3765 var len = targets.length;
3768 state = targets[len];
3769 if(state.isVisible() === false || state.running === true)
3772 if(state.library.initialized === false) {
3773 if(state.library.enabled === true) {
3774 state.library.initialize(NETDATA.chartRefresher);
3778 state.error('chart library "' + state.library_name + '" is not enabled.');
3782 parallel.unshift(state);
3785 if(parallel.length > 0) {
3786 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3787 // this will execute the jobs in parallel
3788 $(parallel).each(function() {
3793 // console.log('auto-refresher nothing to do');
3796 // run the next refresh iteration
3797 setTimeout(NETDATA.chartRefresher,
3798 NETDATA.chartRefresherWaitTime());
3801 NETDATA.parseDom = function(callback) {
3802 NETDATA.options.last_page_scroll = Date.now();
3803 NETDATA.options.updated_dom = false;
3805 var targets = $('div[data-netdata]'); //.filter(':visible');
3807 if(NETDATA.options.debug.main_loop === true)
3808 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3810 NETDATA.options.targets = [];
3811 var len = targets.length;
3813 // the initialization will take care of sizing
3814 // and the "loading..." message
3815 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3818 if(typeof callback === 'function')
3822 // this is the main function - where everything starts
3823 NETDATA.start = function() {
3824 // this should be called only once
3826 NETDATA.options.page_is_visible = true;
3828 $(window).blur(function() {
3829 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3830 NETDATA.options.page_is_visible = false;
3831 if(NETDATA.options.debug.focus === true)
3832 console.log('Lost Focus!');
3836 $(window).focus(function() {
3837 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3838 NETDATA.options.page_is_visible = true;
3839 if(NETDATA.options.debug.focus === true)
3840 console.log('Focus restored!');
3844 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3845 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3846 NETDATA.options.page_is_visible = false;
3847 if(NETDATA.options.debug.focus === true)
3848 console.log('Document has no focus!');
3852 // bootstrap tab switching
3853 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3855 // bootstrap modal switching
3856 var $modal = $('.modal');
3857 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3858 $modal.on('shown.bs.modal', NETDATA.onscroll);
3860 // bootstrap collapse switching
3861 var $collapse = $('.collapse');
3862 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3863 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3865 NETDATA.parseDom(NETDATA.chartRefresher);
3867 // Alarms initialization
3868 setTimeout(NETDATA.alarms.init, 1000);
3870 // Registry initialization
3871 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3873 if(typeof netdataCallback === 'function')
3877 // ----------------------------------------------------------------------------------------------------------------
3880 NETDATA.peityInitialize = function(callback) {
3881 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3883 url: NETDATA.peity_js,
3886 xhrFields: { withCredentials: true } // required for the cookie
3889 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3892 NETDATA.chartLibraries.peity.enabled = false;
3893 NETDATA.error(100, NETDATA.peity_js);
3895 .always(function() {
3896 if(typeof callback === "function")
3901 NETDATA.chartLibraries.peity.enabled = false;
3902 if(typeof callback === "function")
3907 NETDATA.peityChartUpdate = function(state, data) {
3908 state.peity_instance.innerHTML = data.result;
3910 if(state.peity_options.stroke !== state.chartColors()[0]) {
3911 state.peity_options.stroke = state.chartColors()[0];
3912 if(state.chart.chart_type === 'line')
3913 state.peity_options.fill = NETDATA.themes.current.background;
3915 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3918 $(state.peity_instance).peity('line', state.peity_options);
3922 NETDATA.peityChartCreate = function(state, data) {
3923 state.peity_instance = document.createElement('div');
3924 state.element_chart.appendChild(state.peity_instance);
3926 var self = $(state.element);
3927 state.peity_options = {
3928 stroke: NETDATA.themes.current.foreground,
3929 strokeWidth: self.data('peity-strokewidth') || 1,
3930 width: state.chartWidth(),
3931 height: state.chartHeight(),
3932 fill: NETDATA.themes.current.foreground
3935 NETDATA.peityChartUpdate(state, data);
3939 // ----------------------------------------------------------------------------------------------------------------
3942 NETDATA.sparklineInitialize = function(callback) {
3943 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3945 url: NETDATA.sparkline_js,
3948 xhrFields: { withCredentials: true } // required for the cookie
3951 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3954 NETDATA.chartLibraries.sparkline.enabled = false;
3955 NETDATA.error(100, NETDATA.sparkline_js);
3957 .always(function() {
3958 if(typeof callback === "function")
3963 NETDATA.chartLibraries.sparkline.enabled = false;
3964 if(typeof callback === "function")
3969 NETDATA.sparklineChartUpdate = function(state, data) {
3970 state.sparkline_options.width = state.chartWidth();
3971 state.sparkline_options.height = state.chartHeight();
3973 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3977 NETDATA.sparklineChartCreate = function(state, data) {
3978 var self = $(state.element);
3979 var type = self.data('sparkline-type') || 'line';
3980 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3981 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3982 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3983 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3984 var composite = self.data('sparkline-composite') || undefined;
3985 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3986 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3987 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3988 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3989 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3990 var spotColor = self.data('sparkline-spotcolor') || undefined;
3991 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3992 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3993 var spotRadius = self.data('sparkline-spotradius') || undefined;
3994 var valueSpots = self.data('sparkline-valuespots') || undefined;
3995 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3996 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3997 var lineWidth = self.data('sparkline-linewidth') || undefined;
3998 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3999 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
4000 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
4001 var xvalues = self.data('sparkline-xvalues') || undefined;
4002 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
4003 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
4004 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
4005 var disableInteraction = self.data('sparkline-disableinteraction') || false;
4006 var disableTooltips = self.data('sparkline-disabletooltips') || false;
4007 var disableHighlight = self.data('sparkline-disablehighlight') || false;
4008 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
4009 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
4010 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
4011 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
4012 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
4013 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
4014 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
4015 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
4016 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
4017 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
4018 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
4019 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
4020 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
4021 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
4022 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
4023 var animatedZooms = self.data('sparkline-animatedzooms') || false;
4025 if(spotColor === 'disable') spotColor='';
4026 if(minSpotColor === 'disable') minSpotColor='';
4027 if(maxSpotColor === 'disable') maxSpotColor='';
4029 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
4031 state.sparkline_options = {
4033 lineColor: lineColor,
4034 fillColor: fillColor,
4035 chartRangeMin: chartRangeMin,
4036 chartRangeMax: chartRangeMax,
4037 composite: composite,
4038 enableTagOptions: enableTagOptions,
4039 tagOptionPrefix: tagOptionPrefix,
4040 tagValuesAttribute: tagValuesAttribute,
4041 disableHiddenCheck: disableHiddenCheck,
4042 defaultPixelsPerValue: defaultPixelsPerValue,
4043 spotColor: spotColor,
4044 minSpotColor: minSpotColor,
4045 maxSpotColor: maxSpotColor,
4046 spotRadius: spotRadius,
4047 valueSpots: valueSpots,
4048 highlightSpotColor: highlightSpotColor,
4049 highlightLineColor: highlightLineColor,
4050 lineWidth: lineWidth,
4051 normalRangeMin: normalRangeMin,
4052 normalRangeMax: normalRangeMax,
4053 drawNormalOnTop: drawNormalOnTop,
4055 chartRangeClip: chartRangeClip,
4056 chartRangeMinX: chartRangeMinX,
4057 chartRangeMaxX: chartRangeMaxX,
4058 disableInteraction: disableInteraction,
4059 disableTooltips: disableTooltips,
4060 disableHighlight: disableHighlight,
4061 highlightLighten: highlightLighten,
4062 highlightColor: highlightColor,
4063 tooltipContainer: tooltipContainer,
4064 tooltipClassname: tooltipClassname,
4065 tooltipChartTitle: state.title,
4066 tooltipFormat: tooltipFormat,
4067 tooltipPrefix: tooltipPrefix,
4068 tooltipSuffix: tooltipSuffix,
4069 tooltipSkipNull: tooltipSkipNull,
4070 tooltipValueLookups: tooltipValueLookups,
4071 tooltipFormatFieldlist: tooltipFormatFieldlist,
4072 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4073 numberFormatter: numberFormatter,
4074 numberDigitGroupSep: numberDigitGroupSep,
4075 numberDecimalMark: numberDecimalMark,
4076 numberDigitGroupCount: numberDigitGroupCount,
4077 animatedZooms: animatedZooms,
4078 width: state.chartWidth(),
4079 height: state.chartHeight()
4082 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4086 // ----------------------------------------------------------------------------------------------------------------
4093 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4094 if(after < state.netdata_first)
4095 after = state.netdata_first;
4097 if(before > state.netdata_last)
4098 before = state.netdata_last;
4100 state.setMode('zoom');
4101 state.globalSelectionSyncStop();
4102 state.globalSelectionSyncDelay();
4103 state.dygraph_user_action = true;
4104 state.dygraph_force_zoom = true;
4105 state.updateChartPanOrZoom(after, before);
4106 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4109 NETDATA.dygraphSetSelection = function(state, t) {
4110 if(typeof state.dygraph_instance !== 'undefined') {
4111 var r = state.calculateRowForTime(t);
4113 state.dygraph_instance.setSelection(r);
4115 state.dygraph_instance.clearSelection();
4116 state.legendShowUndefined();
4123 NETDATA.dygraphClearSelection = function(state) {
4124 if(typeof state.dygraph_instance !== 'undefined') {
4125 state.dygraph_instance.clearSelection();
4130 NETDATA.dygraphSmoothInitialize = function(callback) {
4132 url: NETDATA.dygraph_smooth_js,
4135 xhrFields: { withCredentials: true } // required for the cookie
4138 NETDATA.dygraph.smooth = true;
4139 smoothPlotter.smoothing = 0.3;
4142 NETDATA.dygraph.smooth = false;
4144 .always(function() {
4145 if(typeof callback === "function")
4150 NETDATA.dygraphInitialize = function(callback) {
4151 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4153 url: NETDATA.dygraph_js,
4156 xhrFields: { withCredentials: true } // required for the cookie
4159 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4162 NETDATA.chartLibraries.dygraph.enabled = false;
4163 NETDATA.error(100, NETDATA.dygraph_js);
4165 .always(function() {
4166 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4167 NETDATA.dygraphSmoothInitialize(callback);
4168 else if(typeof callback === "function")
4173 NETDATA.chartLibraries.dygraph.enabled = false;
4174 if(typeof callback === "function")
4179 NETDATA.dygraphChartUpdate = function(state, data) {
4180 var dygraph = state.dygraph_instance;
4182 if(typeof dygraph === 'undefined')
4183 return NETDATA.dygraphChartCreate(state, data);
4185 // when the chart is not visible, and hidden
4186 // if there is a window resize, dygraph detects
4187 // its element size as 0x0.
4188 // this will make it re-appear properly
4190 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4194 file: data.result.data,
4195 colors: state.chartColors(),
4196 labels: data.result.labels,
4197 labelsDivWidth: state.chartWidth() - 70,
4198 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4201 if(state.dygraph_force_zoom === true) {
4202 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4203 state.log('dygraphChartUpdate() forced zoom update');
4205 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4206 options.isZoomedIgnoreProgrammaticZoom = true;
4207 state.dygraph_force_zoom = false;
4209 else if(state.current.name !== 'auto') {
4210 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4211 state.log('dygraphChartUpdate() loose update');
4214 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4215 state.log('dygraphChartUpdate() strict update');
4217 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4218 options.isZoomedIgnoreProgrammaticZoom = true;
4221 options.valueRange = state.dygraph_options.valueRange;
4223 var oldMax = null, oldMin = null;
4224 if(state.__commonMin !== null) {
4225 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4226 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4228 if(state.__commonMax !== null) {
4229 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4230 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4233 if(state.dygraph_smooth_eligible === true) {
4234 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4235 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4236 NETDATA.dygraphChartCreate(state, data);
4241 dygraph.updateOptions(options);
4244 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4245 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4246 options.valueRange[0] = NETDATA.commonMin.get(state);
4249 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4250 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4251 options.valueRange[1] = NETDATA.commonMax.get(state);
4255 if(redraw === true) {
4256 // state.log('forcing redraw to adapt to common- min/max');
4257 dygraph.updateOptions(options);
4260 // decide the decimal points on the legend of the chart
4261 state.legendFormatValueDecimalsFromMinMax(
4262 state.dygraph_instance.axes_[0].extremeRange[0],
4263 state.dygraph_instance.axes_[0].extremeRange[1]
4266 state.dygraph_last_rendered = Date.now();
4270 NETDATA.dygraphChartCreate = function(state, data) {
4271 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4272 state.log('dygraphChartCreate()');
4274 var self = $(state.element);
4276 var chart_type = self.data('dygraph-type') || state.chart.chart_type;
4277 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4279 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state) === true)?3:4;
4281 var smooth = (NETDATA.dygraph.smooth === true)
4282 ?(self.data('dygraph-smooth') || (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false))
4285 state.dygraph_options = {
4286 colors: self.data('dygraph-colors') || state.chartColors(),
4288 // leave a few pixels empty on the right of the chart
4289 rightGap: self.data('dygraph-rightgap')
4292 showRangeSelector: self.data('dygraph-showrangeselector')
4295 showRoller: self.data('dygraph-showroller')
4298 title: self.data('dygraph-title')
4301 titleHeight: self.data('dygraph-titleheight')
4304 legend: self.data('dygraph-legend')
4305 || 'always', // we need this to get selection events
4307 labels: data.result.labels,
4309 labelsDiv: self.data('dygraph-labelsdiv')
4310 || state.element_legend_childs.hidden,
4312 labelsDivStyles: self.data('dygraph-labelsdivstyles')
4313 || { 'fontSize':'1px' },
4315 labelsDivWidth: self.data('dygraph-labelsdivwidth')
4316 || state.chartWidth() - 70,
4318 labelsSeparateLines: self.data('dygraph-labelsseparatelines')
4321 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues')
4327 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight')
4330 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout')
4333 includeZero: self.data('dygraph-includezero')
4334 || (chart_type === 'stacked'),
4336 xRangePad: self.data('dygraph-xrangepad')
4339 yRangePad: self.data('dygraph-yrangepad')
4342 valueRange: self.data('dygraph-valuerange')
4345 ylabel: state.units,
4347 yLabelWidth: self.data('dygraph-ylabelwidth')
4350 // the function to plot the chart
4353 // The width of the lines connecting data points.
4354 // This can be used to increase the contrast or some graphs.
4355 strokeWidth: self.data('dygraph-strokewidth')
4356 || ((chart_type === 'stacked')?0.1:((smooth === true)?1.5:0.7)),
4358 strokePattern: self.data('dygraph-strokepattern')
4361 // The size of the dot to draw on each point in pixels (see drawPoints).
4362 // A dot is always drawn when a point is "isolated",
4363 // i.e. there is a missing point on either side of it.
4364 // This also controls the size of those dots.
4365 drawPoints: self.data('dygraph-drawpoints')
4368 // Draw points at the edges of gaps in the data.
4369 // This improves visibility of small data segments or other data irregularities.
4370 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints')
4373 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints')
4376 pointSize: self.data('dygraph-pointsize')
4379 // enabling this makes the chart with little square lines
4380 stepPlot: self.data('dygraph-stepplot')
4383 // Draw a border around graph lines to make crossing lines more easily
4384 // distinguishable. Useful for graphs with many lines.
4385 strokeBorderColor: self.data('dygraph-strokebordercolor')
4386 || NETDATA.themes.current.background,
4388 strokeBorderWidth: self.data('dygraph-strokeborderwidth')
4389 || (chart_type === 'stacked')?0.0:0.0,
4391 fillGraph: self.data('dygraph-fillgraph')
4392 || (chart_type === 'area' || chart_type === 'stacked'),
4394 fillAlpha: self.data('dygraph-fillalpha')
4395 || ((chart_type === 'stacked')
4396 ?NETDATA.options.current.color_fill_opacity_stacked
4397 :NETDATA.options.current.color_fill_opacity_area),
4399 stackedGraph: self.data('dygraph-stackedgraph')
4400 || (chart_type === 'stacked'),
4402 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill')
4405 drawAxis: self.data('dygraph-drawaxis')
4408 axisLabelFontSize: self.data('dygraph-axislabelfontsize')
4411 axisLineColor: self.data('dygraph-axislinecolor')
4412 || NETDATA.themes.current.axis,
4414 axisLineWidth: self.data('dygraph-axislinewidth')
4417 drawGrid: self.data('dygraph-drawgrid')
4420 gridLinePattern: self.data('dygraph-gridlinepattern')
4423 gridLineWidth: self.data('dygraph-gridlinewidth')
4426 gridLineColor: self.data('dygraph-gridlinecolor')
4427 || NETDATA.themes.current.grid,
4429 maxNumberWidth: self.data('dygraph-maxnumberwidth')
4432 sigFigs: self.data('dygraph-sigfigs')
4435 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal')
4438 valueFormatter: self.data('dygraph-valueformatter')
4441 highlightCircleSize: self.data('dygraph-highlightcirclesize')
4442 || highlightCircleSize,
4444 highlightSeriesOpts: self.data('dygraph-highlightseriesopts')
4445 || null, // TOO SLOW: { strokeWidth: 1.5 },
4447 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha')
4448 || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4450 pointClickCallback: self.data('dygraph-pointclickcallback')
4453 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4458 ticker: Dygraph.dateTicker,
4459 axisLabelFormatter: function (d, gran) {
4461 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4466 axisLabelFormatter: function (y) {
4468 // unfortunately, we have to call this every single time
4469 state.legendFormatValueDecimalsFromMinMax(
4470 this.axes_[0].extremeRange[0],
4471 this.axes_[0].extremeRange[1]
4474 return state.legendFormatValue(y);
4478 legendFormatter: function(data) {
4479 var elements = state.element_legend_childs;
4481 // if the hidden div is not there
4482 // we are not managing the legend
4483 if(elements.hidden === null) return;
4485 if (typeof data.x !== 'undefined') {
4486 state.legendSetDate(data.x);
4487 var i = data.series.length;
4489 var series = data.series[i];
4490 if(series.isVisible === true)
4491 state.legendSetLabelValue(series.label, series.y);
4493 state.legendSetLabelValue(series.label, null);
4499 drawCallback: function(dygraph, is_initial) {
4500 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4501 state.dygraph_user_action = false;
4503 var x_range = dygraph.xAxisRange();
4504 var after = Math.round(x_range[0]);
4505 var before = Math.round(x_range[1]);
4507 if(NETDATA.options.debug.dygraph === true)
4508 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4510 if(before <= state.netdata_last && after >= state.netdata_first)
4511 state.updateChartPanOrZoom(after, before);
4514 zoomCallback: function(minDate, maxDate, yRanges) {
4517 if(NETDATA.options.debug.dygraph === true)
4518 state.log('dygraphZoomCallback()');
4520 state.globalSelectionSyncStop();
4521 state.globalSelectionSyncDelay();
4522 state.setMode('zoom');
4524 // refresh it to the greatest possible zoom level
4525 state.dygraph_user_action = true;
4526 state.dygraph_force_zoom = true;
4527 state.updateChartPanOrZoom(minDate, maxDate);
4529 highlightCallback: function(event, x, points, row, seriesName) {
4532 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4533 state.log('dygraphHighlightCallback()');
4537 // there is a bug in dygraph when the chart is zoomed enough
4538 // the time it thinks is selected is wrong
4539 // here we calculate the time t based on the row number selected
4541 // var t = state.data_after + row * state.data_update_every;
4542 // 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);
4544 state.globalSelectionSync(x);
4546 // fix legend zIndex using the internal structures of dygraph legend module
4547 // this works, but it is a hack!
4548 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4550 unhighlightCallback: function(event) {
4553 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4554 state.log('dygraphUnhighlightCallback()');
4556 state.unpauseChart();
4557 state.globalSelectionSyncStop();
4559 interactionModel : {
4560 mousedown: function(event, dygraph, context) {
4561 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4562 state.log('interactionModel.mousedown()');
4564 state.dygraph_user_action = true;
4565 state.globalSelectionSyncStop();
4567 if(NETDATA.options.debug.dygraph === true)
4568 state.log('dygraphMouseDown()');
4570 // Right-click should not initiate a zoom.
4571 if(event.button && event.button === 2) return;
4573 context.initializeMouseDown(event, dygraph, context);
4575 if(event.button && event.button === 1) {
4576 if (event.altKey || event.shiftKey) {
4577 state.setMode('pan');
4578 state.globalSelectionSyncDelay();
4579 Dygraph.startPan(event, dygraph, context);
4582 state.setMode('zoom');
4583 state.globalSelectionSyncDelay();
4584 Dygraph.startZoom(event, dygraph, context);
4588 if (event.altKey || event.shiftKey) {
4589 state.setMode('zoom');
4590 state.globalSelectionSyncDelay();
4591 Dygraph.startZoom(event, dygraph, context);
4594 state.setMode('pan');
4595 state.globalSelectionSyncDelay();
4596 Dygraph.startPan(event, dygraph, context);
4600 mousemove: function(event, dygraph, context) {
4601 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4602 state.log('interactionModel.mousemove()');
4604 if(context.isPanning) {
4605 state.dygraph_user_action = true;
4606 state.globalSelectionSyncStop();
4607 state.globalSelectionSyncDelay();
4608 state.setMode('pan');
4609 context.is2DPan = false;
4610 Dygraph.movePan(event, dygraph, context);
4612 else if(context.isZooming) {
4613 state.dygraph_user_action = true;
4614 state.globalSelectionSyncStop();
4615 state.globalSelectionSyncDelay();
4616 state.setMode('zoom');
4617 Dygraph.moveZoom(event, dygraph, context);
4620 mouseup: function(event, dygraph, context) {
4621 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4622 state.log('interactionModel.mouseup()');
4624 if (context.isPanning) {
4625 state.dygraph_user_action = true;
4626 state.globalSelectionSyncDelay();
4627 Dygraph.endPan(event, dygraph, context);
4629 else if (context.isZooming) {
4630 state.dygraph_user_action = true;
4631 state.globalSelectionSyncDelay();
4632 Dygraph.endZoom(event, dygraph, context);
4635 click: function(event, dygraph, context) {
4639 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4640 state.log('interactionModel.click()');
4642 event.preventDefault();
4644 dblclick: function(event, dygraph, context) {
4649 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4650 state.log('interactionModel.dblclick()');
4651 NETDATA.resetAllCharts(state);
4653 wheel: function(event, dygraph, context) {
4656 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4657 state.log('interactionModel.wheel()');
4659 // Take the offset of a mouse event on the dygraph canvas and
4660 // convert it to a pair of percentages from the bottom left.
4661 // (Not top left, bottom is where the lower value is.)
4662 function offsetToPercentage(g, offsetX, offsetY) {
4663 // This is calculating the pixel offset of the leftmost date.
4664 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4665 var yar0 = g.yAxisRange(0);
4667 // This is calculating the pixel of the highest value. (Top pixel)
4668 var yOffset = g.toDomCoords(null, yar0[1])[1];
4670 // x y w and h are relative to the corner of the drawing area,
4671 // so that the upper corner of the drawing area is (0, 0).
4672 var x = offsetX - xOffset;
4673 var y = offsetY - yOffset;
4675 // This is computing the rightmost pixel, effectively defining the
4677 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4679 // This is computing the lowest pixel, effectively defining the height.
4680 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4682 // Percentage from the left.
4683 var xPct = w === 0 ? 0 : (x / w);
4684 // Percentage from the top.
4685 var yPct = h === 0 ? 0 : (y / h);
4687 // The (1-) part below changes it from "% distance down from the top"
4688 // to "% distance up from the bottom".
4689 return [xPct, (1-yPct)];
4692 // Adjusts [x, y] toward each other by zoomInPercentage%
4693 // Split it so the left/bottom axis gets xBias/yBias of that change and
4694 // tight/top gets (1-xBias)/(1-yBias) of that change.
4696 // If a bias is missing it splits it down the middle.
4697 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4698 xBias = xBias || 0.5;
4699 yBias = yBias || 0.5;
4701 function adjustAxis(axis, zoomInPercentage, bias) {
4702 var delta = axis[1] - axis[0];
4703 var increment = delta * zoomInPercentage;
4704 var foo = [increment * bias, increment * (1-bias)];
4706 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4709 var yAxes = g.yAxisRanges();
4711 for (var i = 0; i < yAxes.length; i++) {
4712 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4715 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4718 if(event.altKey || event.shiftKey) {
4719 state.dygraph_user_action = true;
4721 state.globalSelectionSyncStop();
4722 state.globalSelectionSyncDelay();
4724 // http://dygraphs.com/gallery/interaction-api.js
4726 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4728 normal_def = event.wheelDelta / 40;
4731 normal_def = event.deltaY * -1.2;
4733 var normal = (event.detail) ? event.detail * -1 : normal_def;
4734 var percentage = normal / 50;
4736 if (!(event.offsetX && event.offsetY)){
4737 event.offsetX = event.layerX - event.target.offsetLeft;
4738 event.offsetY = event.layerY - event.target.offsetTop;
4741 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4742 var xPct = percentages[0];
4743 var yPct = percentages[1];
4745 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4746 var after = new_x_range[0];
4747 var before = new_x_range[1];
4749 var first = state.netdata_first + state.data_update_every;
4750 var last = state.netdata_last + state.data_update_every;
4753 after -= (before - last);
4760 state.setMode('zoom');
4761 if(state.updateChartPanOrZoom(after, before) === true)
4762 dygraph.updateOptions({ dateWindow: [ after, before ] });
4764 event.preventDefault();
4767 touchstart: function(event, dygraph, context) {
4768 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4769 state.log('interactionModel.touchstart()');
4771 state.dygraph_user_action = true;
4772 state.setMode('zoom');
4775 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4777 // we overwrite the touch directions at the end, to overwrite
4778 // the internal default of dygraph
4779 context.touchDirections = { x: true, y: false };
4781 state.dygraph_last_touch_start = Date.now();
4782 state.dygraph_last_touch_move = 0;
4784 if(typeof event.touches[0].pageX === 'number')
4785 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4787 state.dygraph_last_touch_page_x = 0;
4789 touchmove: function(event, dygraph, context) {
4790 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4791 state.log('interactionModel.touchmove()');
4793 state.dygraph_user_action = true;
4794 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4796 state.dygraph_last_touch_move = Date.now();
4798 touchend: function(event, dygraph, context) {
4799 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4800 state.log('interactionModel.touchend()');
4802 state.dygraph_user_action = true;
4803 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4805 // if it didn't move, it is a selection
4806 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4807 // internal api of dygraph
4808 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4809 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4810 if(NETDATA.dygraphSetSelection(state, t) === true)
4811 state.globalSelectionSync(t);
4814 // if it was double tap within double click time, reset the charts
4815 var now = Date.now();
4816 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4817 if(state.dygraph_last_touch_move === 0) {
4818 var dt = now - state.dygraph_last_touch_end;
4819 if(dt <= NETDATA.options.current.double_click_speed)
4820 NETDATA.resetAllCharts(state);
4824 // remember the timestamp of the last touch end
4825 state.dygraph_last_touch_end = now;
4830 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4831 state.dygraph_options.drawGrid = false;
4832 state.dygraph_options.drawAxis = false;
4833 state.dygraph_options.title = undefined;
4834 state.dygraph_options.ylabel = undefined;
4835 state.dygraph_options.yLabelWidth = 0;
4836 state.dygraph_options.labelsDivWidth = 120;
4837 state.dygraph_options.labelsDivStyles.width = '120px';
4838 state.dygraph_options.labelsSeparateLines = true;
4839 state.dygraph_options.rightGap = 0;
4840 state.dygraph_options.yRangePad = 1;
4843 if(smooth === true) {
4844 state.dygraph_smooth_eligible = true;
4846 if(NETDATA.options.current.smooth_plot === true)
4847 state.dygraph_options.plotter = smoothPlotter;
4849 else state.dygraph_smooth_eligible = false;
4851 state.dygraph_instance = new Dygraph(state.element_chart,
4852 data.result.data, state.dygraph_options);
4854 state.dygraph_force_zoom = false;
4855 state.dygraph_user_action = false;
4856 state.dygraph_last_rendered = Date.now();
4858 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4859 state.__commonMin = self.data('common-min') || null;
4860 state.__commonMax = self.data('common-max') || null;
4863 state.log('incompatible version of Dygraph detected');
4864 state.__commonMin = null;
4865 state.__commonMax = null;
4868 // decide the decimal points on the legend of the chart
4869 state.legendFormatValueDecimalsFromMinMax(
4870 state.dygraph_instance.axes_[0].extremeRange[0],
4871 state.dygraph_instance.axes_[0].extremeRange[1]
4877 // ----------------------------------------------------------------------------------------------------------------
4880 NETDATA.morrisInitialize = function(callback) {
4881 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4883 // morris requires raphael
4884 if(!NETDATA.chartLibraries.raphael.initialized) {
4885 if(NETDATA.chartLibraries.raphael.enabled) {
4886 NETDATA.raphaelInitialize(function() {
4887 NETDATA.morrisInitialize(callback);
4891 NETDATA.chartLibraries.morris.enabled = false;
4892 if(typeof callback === "function")
4897 NETDATA._loadCSS(NETDATA.morris_css);
4900 url: NETDATA.morris_js,
4903 xhrFields: { withCredentials: true } // required for the cookie
4906 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4909 NETDATA.chartLibraries.morris.enabled = false;
4910 NETDATA.error(100, NETDATA.morris_js);
4912 .always(function() {
4913 if(typeof callback === "function")
4919 NETDATA.chartLibraries.morris.enabled = false;
4920 if(typeof callback === "function")
4925 NETDATA.morrisChartUpdate = function(state, data) {
4926 state.morris_instance.setData(data.result.data);
4930 NETDATA.morrisChartCreate = function(state, data) {
4932 state.morris_options = {
4933 element: state.element_chart.id,
4934 data: data.result.data,
4936 ykeys: data.dimension_names,
4937 labels: data.dimension_names,
4943 continuousLine: false,
4944 behaveLikeLine: false
4947 if(state.chart.chart_type === 'line')
4948 state.morris_instance = new Morris.Line(state.morris_options);
4950 else if(state.chart.chart_type === 'area') {
4951 state.morris_options.behaveLikeLine = true;
4952 state.morris_instance = new Morris.Area(state.morris_options);
4955 state.morris_instance = new Morris.Area(state.morris_options);
4960 // ----------------------------------------------------------------------------------------------------------------
4963 NETDATA.raphaelInitialize = function(callback) {
4964 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4966 url: NETDATA.raphael_js,
4969 xhrFields: { withCredentials: true } // required for the cookie
4972 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4975 NETDATA.chartLibraries.raphael.enabled = false;
4976 NETDATA.error(100, NETDATA.raphael_js);
4978 .always(function() {
4979 if(typeof callback === "function")
4984 NETDATA.chartLibraries.raphael.enabled = false;
4985 if(typeof callback === "function")
4990 NETDATA.raphaelChartUpdate = function(state, data) {
4991 $(state.element_chart).raphael(data.result, {
4992 width: state.chartWidth(),
4993 height: state.chartHeight()
4999 NETDATA.raphaelChartCreate = function(state, data) {
5000 $(state.element_chart).raphael(data.result, {
5001 width: state.chartWidth(),
5002 height: state.chartHeight()
5008 // ----------------------------------------------------------------------------------------------------------------
5011 NETDATA.c3Initialize = function(callback) {
5012 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
5015 if(!NETDATA.chartLibraries.d3.initialized) {
5016 if(NETDATA.chartLibraries.d3.enabled) {
5017 NETDATA.d3Initialize(function() {
5018 NETDATA.c3Initialize(callback);
5022 NETDATA.chartLibraries.c3.enabled = false;
5023 if(typeof callback === "function")
5028 NETDATA._loadCSS(NETDATA.c3_css);
5034 xhrFields: { withCredentials: true } // required for the cookie
5037 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
5040 NETDATA.chartLibraries.c3.enabled = false;
5041 NETDATA.error(100, NETDATA.c3_js);
5043 .always(function() {
5044 if(typeof callback === "function")
5050 NETDATA.chartLibraries.c3.enabled = false;
5051 if(typeof callback === "function")
5056 NETDATA.c3ChartUpdate = function(state, data) {
5057 state.c3_instance.destroy();
5058 return NETDATA.c3ChartCreate(state, data);
5060 //state.c3_instance.load({
5061 // rows: data.result,
5068 NETDATA.c3ChartCreate = function(state, data) {
5070 state.element_chart.id = 'c3-' + state.uuid;
5071 // console.log('id = ' + state.element_chart.id);
5073 state.c3_instance = c3.generate({
5074 bindto: '#' + state.element_chart.id,
5076 width: state.chartWidth(),
5077 height: state.chartHeight()
5080 pattern: state.chartColors()
5085 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
5091 format: function(x) {
5092 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
5119 // console.log(state.c3_instance);
5124 // ----------------------------------------------------------------------------------------------------------------
5127 NETDATA.d3Initialize = function(callback) {
5128 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
5133 xhrFields: { withCredentials: true } // required for the cookie
5136 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
5139 NETDATA.chartLibraries.d3.enabled = false;
5140 NETDATA.error(100, NETDATA.d3_js);
5142 .always(function() {
5143 if(typeof callback === "function")
5148 NETDATA.chartLibraries.d3.enabled = false;
5149 if(typeof callback === "function")
5154 NETDATA.d3ChartUpdate = function(state, data) {
5161 NETDATA.d3ChartCreate = function(state, data) {
5168 // ----------------------------------------------------------------------------------------------------------------
5171 NETDATA.googleInitialize = function(callback) {
5172 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5174 url: NETDATA.google_js,
5177 xhrFields: { withCredentials: true } // required for the cookie
5180 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5181 google.load('visualization', '1.1', {
5182 'packages': ['corechart', 'controls'],
5183 'callback': callback
5187 NETDATA.chartLibraries.google.enabled = false;
5188 NETDATA.error(100, NETDATA.google_js);
5189 if(typeof callback === "function")
5194 NETDATA.chartLibraries.google.enabled = false;
5195 if(typeof callback === "function")
5200 NETDATA.googleChartUpdate = function(state, data) {
5201 var datatable = new google.visualization.DataTable(data.result);
5202 state.google_instance.draw(datatable, state.google_options);
5206 NETDATA.googleChartCreate = function(state, data) {
5207 var datatable = new google.visualization.DataTable(data.result);
5209 state.google_options = {
5210 colors: state.chartColors(),
5212 // do not set width, height - the chart resizes itself
5213 //width: state.chartWidth(),
5214 //height: state.chartHeight(),
5219 // title: "Time of Day",
5220 // format:'HH:mm:ss',
5221 viewWindowMode: 'maximized',
5233 viewWindowMode: 'pretty',
5248 focusTarget: 'category',
5255 titlePosition: 'out',
5266 curveType: 'function',
5271 switch(state.chart.chart_type) {
5273 state.google_options.vAxis.viewWindowMode = 'maximized';
5274 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5275 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5279 state.google_options.isStacked = true;
5280 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5281 state.google_options.vAxis.viewWindowMode = 'maximized';
5282 state.google_options.vAxis.minValue = null;
5283 state.google_options.vAxis.maxValue = null;
5284 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5289 state.google_options.lineWidth = 2;
5290 state.google_instance = new google.visualization.LineChart(state.element_chart);
5294 state.google_instance.draw(datatable, state.google_options);
5298 // ----------------------------------------------------------------------------------------------------------------
5300 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5301 if(typeof value !== 'number') value = 0;
5302 if(typeof min !== 'number') min = 0;
5303 if(typeof max !== 'number') max = 0;
5305 if(min > value) min = value;
5306 if(max < value) max = value;
5308 // make sure it is zero based
5309 if(min > 0) min = 0;
5310 if(max < 0) max = 0;
5315 pcent = Math.round(value * 100 / max);
5316 if(pcent === 0) pcent = 0.1;
5320 pcent = Math.round(-value * 100 / min);
5321 if(pcent === 0) pcent = -0.1;
5327 // ----------------------------------------------------------------------------------------------------------------
5330 NETDATA.easypiechartInitialize = function(callback) {
5331 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5333 url: NETDATA.easypiechart_js,
5336 xhrFields: { withCredentials: true } // required for the cookie
5339 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5342 NETDATA.chartLibraries.easypiechart.enabled = false;
5343 NETDATA.error(100, NETDATA.easypiechart_js);
5345 .always(function() {
5346 if(typeof callback === "function")
5351 NETDATA.chartLibraries.easypiechart.enabled = false;
5352 if(typeof callback === "function")
5357 NETDATA.easypiechartClearSelection = function(state) {
5358 if(typeof state.easyPieChartEvent !== 'undefined') {
5359 if(state.easyPieChartEvent.timer !== undefined) {
5360 clearTimeout(state.easyPieChartEvent.timer);
5363 state.easyPieChartEvent.timer = undefined;
5366 if(state.isAutoRefreshable() === true && state.data !== null) {
5367 NETDATA.easypiechartChartUpdate(state, state.data);
5370 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5371 state.easyPieChart_instance.update(0);
5373 state.easyPieChart_instance.enableAnimation();
5378 NETDATA.easypiechartSetSelection = function(state, t) {
5379 if(state.timeIsVisible(t) !== true)
5380 return NETDATA.easypiechartClearSelection(state);
5382 var slot = state.calculateRowForTime(t);
5383 if(slot < 0 || slot >= state.data.result.length)
5384 return NETDATA.easypiechartClearSelection(state);
5386 if(typeof state.easyPieChartEvent === 'undefined') {
5387 state.easyPieChartEvent = {
5394 var value = state.data.result[state.data.result.length - 1 - slot];
5395 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5396 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5397 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5399 state.easyPieChartEvent.value = value;
5400 state.easyPieChartEvent.pcent = pcent;
5401 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5403 if(state.easyPieChartEvent.timer === undefined) {
5404 state.easyPieChart_instance.disableAnimation();
5406 state.easyPieChartEvent.timer = setTimeout(function() {
5407 state.easyPieChartEvent.timer = undefined;
5408 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5409 }, NETDATA.options.current.charts_selection_animation_delay);
5415 NETDATA.easypiechartChartUpdate = function(state, data) {
5416 var value, min, max, pcent;
5418 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5423 value = data.result[0];
5424 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5425 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5426 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5429 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5430 state.easyPieChart_instance.update(pcent);
5434 NETDATA.easypiechartChartCreate = function(state, data) {
5435 var self = $(state.element);
5436 var chart = $(state.element_chart);
5438 var value = data.result[0];
5439 var min = self.data('easypiechart-min-value') || null;
5440 var max = self.data('easypiechart-max-value') || null;
5441 var adjust = self.data('easypiechart-adjust') || null;
5444 min = NETDATA.commonMin.get(state);
5445 state.easyPieChartMin = null;
5448 state.easyPieChartMin = min;
5451 max = NETDATA.commonMax.get(state);
5452 state.easyPieChartMax = null;
5455 state.easyPieChartMax = max;
5457 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5459 chart.data('data-percent', pcent);
5463 case 'width': size = state.chartHeight(); break;
5464 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5465 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5467 default: size = state.chartWidth(); break;
5469 state.element.style.width = size + 'px';
5470 state.element.style.height = size + 'px';
5472 var stroke = Math.floor(size / 22);
5473 if(stroke < 3) stroke = 2;
5475 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5476 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5477 state.easyPieChartLabel = document.createElement('span');
5478 state.easyPieChartLabel.className = 'easyPieChartLabel';
5479 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5480 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5481 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5482 state.element_chart.appendChild(state.easyPieChartLabel);
5484 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5485 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5486 state.easyPieChartTitle = document.createElement('span');
5487 state.easyPieChartTitle.className = 'easyPieChartTitle';
5488 state.easyPieChartTitle.innerText = state.title;
5489 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5490 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5491 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5492 state.element_chart.appendChild(state.easyPieChartTitle);
5494 var unitfontsize = Math.round(titlefontsize * 0.9);
5495 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5496 state.easyPieChartUnits = document.createElement('span');
5497 state.easyPieChartUnits.className = 'easyPieChartUnits';
5498 state.easyPieChartUnits.innerText = state.units;
5499 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5500 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5501 state.element_chart.appendChild(state.easyPieChartUnits);
5503 var barColor = self.data('easypiechart-barcolor');
5504 if(typeof barColor === 'undefined' || barColor === null)
5505 barColor = state.chartColors()[0];
5507 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5508 var tmp = eval(barColor);
5509 if(typeof tmp === 'function')
5513 chart.easyPieChart({
5515 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5516 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5517 scaleLength: self.data('easypiechart-scalelength') || 5,
5518 lineCap: self.data('easypiechart-linecap') || 'round',
5519 lineWidth: self.data('easypiechart-linewidth') || stroke,
5520 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5521 size: self.data('easypiechart-size') || size,
5522 rotate: self.data('easypiechart-rotate') || 0,
5523 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5524 easing: self.data('easypiechart-easing') || undefined
5527 // when we just re-create the chart
5528 // do not animate the first update
5530 if(typeof state.easyPieChart_instance !== 'undefined')
5533 state.easyPieChart_instance = chart.data('easyPieChart');
5534 if(animate === false) state.easyPieChart_instance.disableAnimation();
5535 state.easyPieChart_instance.update(pcent);
5536 if(animate === false) state.easyPieChart_instance.enableAnimation();
5540 // ----------------------------------------------------------------------------------------------------------------
5543 NETDATA.gaugeInitialize = function(callback) {
5544 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5546 url: NETDATA.gauge_js,
5549 xhrFields: { withCredentials: true } // required for the cookie
5552 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5555 NETDATA.chartLibraries.gauge.enabled = false;
5556 NETDATA.error(100, NETDATA.gauge_js);
5558 .always(function() {
5559 if(typeof callback === "function")
5564 NETDATA.chartLibraries.gauge.enabled = false;
5565 if(typeof callback === "function")
5570 NETDATA.gaugeAnimation = function(state, status) {
5573 if(typeof status === 'boolean' && status === false)
5575 else if(typeof status === 'number')
5578 // console.log('gauge speed ' + speed);
5579 state.gauge_instance.animationSpeed = speed;
5580 state.___gaugeOld__.speed = speed;
5583 NETDATA.gaugeSet = function(state, value, min, max) {
5584 if(typeof value !== 'number') value = 0;
5585 if(typeof min !== 'number') min = 0;
5586 if(typeof max !== 'number') max = 0;
5587 if(value > max) max = value;
5588 if(value < min) min = value;
5594 else if(min === max)
5597 // gauge.js has an issue if the needle
5598 // is smaller than min or larger than max
5599 // when we set the new values
5600 // the needle will go crazy
5602 // to prevent it, we always feed it
5603 // with a percentage, so that the needle
5604 // is always between min and max
5605 var pcent = (value - min) * 100 / (max - min);
5607 // bug fix for gauge.js 1.3.1
5608 // if the value is the absolute min or max, the chart is broken
5609 if(pcent < 0.001) pcent = 0.001;
5610 if(pcent > 99.999) pcent = 99.999;
5612 state.gauge_instance.set(pcent);
5613 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5615 state.___gaugeOld__.value = value;
5616 state.___gaugeOld__.min = min;
5617 state.___gaugeOld__.max = max;
5620 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5621 if(state.___gaugeOld__.valueLabel !== value) {
5622 state.___gaugeOld__.valueLabel = value;
5623 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5625 if(state.___gaugeOld__.minLabel !== min) {
5626 state.___gaugeOld__.minLabel = min;
5627 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5629 if(state.___gaugeOld__.maxLabel !== max) {
5630 state.___gaugeOld__.maxLabel = max;
5631 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5635 NETDATA.gaugeClearSelection = function(state) {
5636 if(typeof state.gaugeEvent !== 'undefined') {
5637 if(state.gaugeEvent.timer !== undefined) {
5638 clearTimeout(state.gaugeEvent.timer);
5641 state.gaugeEvent.timer = undefined;
5644 if(state.isAutoRefreshable() === true && state.data !== null) {
5645 NETDATA.gaugeChartUpdate(state, state.data);
5648 NETDATA.gaugeAnimation(state, false);
5649 NETDATA.gaugeSet(state, null, null, null);
5650 NETDATA.gaugeSetLabels(state, null, null, null);
5653 NETDATA.gaugeAnimation(state, true);
5657 NETDATA.gaugeSetSelection = function(state, t) {
5658 if(state.timeIsVisible(t) !== true)
5659 return NETDATA.gaugeClearSelection(state);
5661 var slot = state.calculateRowForTime(t);
5662 if(slot < 0 || slot >= state.data.result.length)
5663 return NETDATA.gaugeClearSelection(state);
5665 if(typeof state.gaugeEvent === 'undefined') {
5666 state.gaugeEvent = {
5674 var value = state.data.result[state.data.result.length - 1 - slot];
5675 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5676 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5678 // make sure it is zero based
5679 if(min > 0) min = 0;
5680 if(max < 0) max = 0;
5682 state.gaugeEvent.value = value;
5683 state.gaugeEvent.min = min;
5684 state.gaugeEvent.max = max;
5685 NETDATA.gaugeSetLabels(state, value, min, max);
5687 if(state.gaugeEvent.timer === undefined) {
5688 NETDATA.gaugeAnimation(state, false);
5690 state.gaugeEvent.timer = setTimeout(function() {
5691 state.gaugeEvent.timer = undefined;
5692 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5693 }, NETDATA.options.current.charts_selection_animation_delay);
5699 NETDATA.gaugeChartUpdate = function(state, data) {
5700 var value, min, max;
5702 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5706 NETDATA.gaugeSetLabels(state, null, null, null);
5709 value = data.result[0];
5710 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5711 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5712 if(value < min) min = value;
5713 if(value > max) max = value;
5715 // make sure it is zero based
5716 if(min > 0) min = 0;
5717 if(max < 0) max = 0;
5719 NETDATA.gaugeSetLabels(state, value, min, max);
5722 NETDATA.gaugeSet(state, value, min, max);
5726 NETDATA.gaugeChartCreate = function(state, data) {
5727 var self = $(state.element);
5728 // var chart = $(state.element_chart);
5730 var value = data.result[0];
5731 var min = self.data('gauge-min-value') || null;
5732 var max = self.data('gauge-max-value') || null;
5733 var adjust = self.data('gauge-adjust') || null;
5734 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5735 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5736 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5737 var stopColor = self.data('gauge-stop-color') || void 0;
5738 var generateGradient = self.data('gauge-generate-gradient') || false;
5741 min = NETDATA.commonMin.get(state);
5742 state.gaugeMin = null;
5745 state.gaugeMin = min;
5748 max = NETDATA.commonMax.get(state);
5749 state.gaugeMax = null;
5752 state.gaugeMax = max;
5754 // make sure it is zero based
5755 if(min > 0) min = 0;
5756 if(max < 0) max = 0;
5758 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5760 // case 'width': width = height * ratio; break;
5762 // default: height = width / ratio; break;
5764 //state.element.style.width = width.toString() + 'px';
5765 //state.element.style.height = height.toString() + 'px';
5770 lines: 12, // The number of lines to draw
5771 angle: 0.15, // The span of the gauge arc
5772 lineWidth: 0.50, // The line thickness
5773 radiusScale: 0.85, // Relative radius
5775 length: 0.8, // 0.9 The radius of the inner circle
5776 strokeWidth: 0.035, // The rotation offset
5777 color: pointerColor // Fill color
5779 limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
5780 limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
5781 colorStart: startColor, // Colors
5782 colorStop: stopColor, // just experiment with them
5783 strokeColor: strokeColor, // to see which ones work best for you
5784 generateGradient: (generateGradient === true),
5786 highDpiSupport: true // High resolution support
5789 if (generateGradient.constructor === Array) {
5791 // data-gauge-generate-gradient="[0, 50, 100]"
5792 // data-gauge-gradient-percent-color-0="#FFFFFF"
5793 // data-gauge-gradient-percent-color-50="#999900"
5794 // data-gauge-gradient-percent-color-100="#000000"
5796 options.percentColors = [];
5797 var len = generateGradient.length;
5799 var pcent = generateGradient[len];
5800 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5801 if(color !== false) {
5805 options.percentColors.unshift(a);
5808 if(options.percentColors.length === 0)
5809 delete options.percentColors;
5811 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5812 //noinspection PointlessArithmeticExpressionJS
5813 options.percentColors = [
5814 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5815 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5816 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5817 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5818 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5819 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5820 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5821 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5822 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5823 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5824 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5827 state.gauge_canvas = document.createElement('canvas');
5828 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5829 state.gauge_canvas.className = 'gaugeChart';
5830 state.gauge_canvas.width = width;
5831 state.gauge_canvas.height = height;
5832 state.element_chart.appendChild(state.gauge_canvas);
5834 var valuefontsize = Math.floor(height / 6);
5835 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5836 state.gaugeChartLabel = document.createElement('span');
5837 state.gaugeChartLabel.className = 'gaugeChartLabel';
5838 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5839 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5840 state.element_chart.appendChild(state.gaugeChartLabel);
5842 var titlefontsize = Math.round(valuefontsize / 2);
5844 state.gaugeChartTitle = document.createElement('span');
5845 state.gaugeChartTitle.className = 'gaugeChartTitle';
5846 state.gaugeChartTitle.innerText = state.title;
5847 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5848 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5849 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5850 state.element_chart.appendChild(state.gaugeChartTitle);
5852 var unitfontsize = Math.round(titlefontsize * 0.9);
5853 state.gaugeChartUnits = document.createElement('span');
5854 state.gaugeChartUnits.className = 'gaugeChartUnits';
5855 state.gaugeChartUnits.innerText = state.units;
5856 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5857 state.element_chart.appendChild(state.gaugeChartUnits);
5859 state.gaugeChartMin = document.createElement('span');
5860 state.gaugeChartMin.className = 'gaugeChartMin';
5861 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5862 state.element_chart.appendChild(state.gaugeChartMin);
5864 state.gaugeChartMax = document.createElement('span');
5865 state.gaugeChartMax.className = 'gaugeChartMax';
5866 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5867 state.element_chart.appendChild(state.gaugeChartMax);
5869 // when we just re-create the chart
5870 // do not animate the first update
5872 if(typeof state.gauge_instance !== 'undefined')
5875 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5877 state.___gaugeOld__ = {
5886 // we will always feed a percentage
5887 state.gauge_instance.minValue = 0;
5888 state.gauge_instance.maxValue = 100;
5890 NETDATA.gaugeAnimation(state, animate);
5891 NETDATA.gaugeSet(state, value, min, max);
5892 NETDATA.gaugeSetLabels(state, value, min, max);
5893 NETDATA.gaugeAnimation(state, true);
5897 // ----------------------------------------------------------------------------------------------------------------
5898 // Charts Libraries Registration
5900 NETDATA.chartLibraries = {
5902 initialize: NETDATA.dygraphInitialize,
5903 create: NETDATA.dygraphChartCreate,
5904 update: NETDATA.dygraphChartUpdate,
5905 resize: function(state) {
5906 if(typeof state.dygraph_instance.resize === 'function')
5907 state.dygraph_instance.resize();
5909 setSelection: NETDATA.dygraphSetSelection,
5910 clearSelection: NETDATA.dygraphClearSelection,
5911 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5914 format: function(state) { void(state); return 'json'; },
5915 options: function(state) { void(state); return 'ms|flip'; },
5916 legend: function(state) {
5917 return (this.isSparkline(state) === false)?'right-side':null;
5919 autoresize: function(state) { void(state); return true; },
5920 max_updates_to_recreate: function(state) { void(state); return 5000; },
5921 track_colors: function(state) { void(state); return true; },
5922 pixels_per_point: function(state) {
5923 return (this.isSparkline(state) === false)?3:2;
5925 isSparkline: function(state) {
5926 if(typeof state.dygraph_sparkline === 'undefined') {
5927 var t = $(state.element).data('dygraph-theme');
5928 state.dygraph_sparkline = (t === 'sparkline');
5930 return state.dygraph_sparkline;
5934 initialize: NETDATA.sparklineInitialize,
5935 create: NETDATA.sparklineChartCreate,
5936 update: NETDATA.sparklineChartUpdate,
5938 setSelection: undefined, // function(state, t) { void(state); return true; },
5939 clearSelection: undefined, // function(state) { void(state); return true; },
5940 toolboxPanAndZoom: null,
5943 format: function(state) { void(state); return 'array'; },
5944 options: function(state) { void(state); return 'flip|abs'; },
5945 legend: function(state) { void(state); return null; },
5946 autoresize: function(state) { void(state); return false; },
5947 max_updates_to_recreate: function(state) { void(state); return 5000; },
5948 track_colors: function(state) { void(state); return false; },
5949 pixels_per_point: function(state) { void(state); return 3; }
5952 initialize: NETDATA.peityInitialize,
5953 create: NETDATA.peityChartCreate,
5954 update: NETDATA.peityChartUpdate,
5956 setSelection: undefined, // function(state, t) { void(state); return true; },
5957 clearSelection: undefined, // function(state) { void(state); return true; },
5958 toolboxPanAndZoom: null,
5961 format: function(state) { void(state); return 'ssvcomma'; },
5962 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5963 legend: function(state) { void(state); return null; },
5964 autoresize: function(state) { void(state); return false; },
5965 max_updates_to_recreate: function(state) { void(state); return 5000; },
5966 track_colors: function(state) { void(state); return false; },
5967 pixels_per_point: function(state) { void(state); return 3; }
5970 initialize: NETDATA.morrisInitialize,
5971 create: NETDATA.morrisChartCreate,
5972 update: NETDATA.morrisChartUpdate,
5974 setSelection: undefined, // function(state, t) { void(state); return true; },
5975 clearSelection: undefined, // function(state) { void(state); return true; },
5976 toolboxPanAndZoom: null,
5979 format: function(state) { void(state); return 'json'; },
5980 options: function(state) { void(state); return 'objectrows|ms'; },
5981 legend: function(state) { void(state); return null; },
5982 autoresize: function(state) { void(state); return false; },
5983 max_updates_to_recreate: function(state) { void(state); return 50; },
5984 track_colors: function(state) { void(state); return false; },
5985 pixels_per_point: function(state) { void(state); return 15; }
5988 initialize: NETDATA.googleInitialize,
5989 create: NETDATA.googleChartCreate,
5990 update: NETDATA.googleChartUpdate,
5992 setSelection: undefined, //function(state, t) { void(state); return true; },
5993 clearSelection: undefined, //function(state) { void(state); return true; },
5994 toolboxPanAndZoom: null,
5997 format: function(state) { void(state); return 'datatable'; },
5998 options: function(state) { void(state); return ''; },
5999 legend: function(state) { void(state); return null; },
6000 autoresize: function(state) { void(state); return false; },
6001 max_updates_to_recreate: function(state) { void(state); return 300; },
6002 track_colors: function(state) { void(state); return false; },
6003 pixels_per_point: function(state) { void(state); return 4; }
6006 initialize: NETDATA.raphaelInitialize,
6007 create: NETDATA.raphaelChartCreate,
6008 update: NETDATA.raphaelChartUpdate,
6010 setSelection: undefined, // function(state, t) { void(state); return true; },
6011 clearSelection: undefined, // function(state) { void(state); return true; },
6012 toolboxPanAndZoom: null,
6015 format: function(state) { void(state); return 'json'; },
6016 options: function(state) { void(state); return ''; },
6017 legend: function(state) { void(state); return null; },
6018 autoresize: function(state) { void(state); return false; },
6019 max_updates_to_recreate: function(state) { void(state); return 5000; },
6020 track_colors: function(state) { void(state); return false; },
6021 pixels_per_point: function(state) { void(state); return 3; }
6024 initialize: NETDATA.c3Initialize,
6025 create: NETDATA.c3ChartCreate,
6026 update: NETDATA.c3ChartUpdate,
6028 setSelection: undefined, // function(state, t) { void(state); return true; },
6029 clearSelection: undefined, // function(state) { void(state); return true; },
6030 toolboxPanAndZoom: null,
6033 format: function(state) { void(state); return 'csvjsonarray'; },
6034 options: function(state) { void(state); return 'milliseconds'; },
6035 legend: function(state) { void(state); return null; },
6036 autoresize: function(state) { void(state); return false; },
6037 max_updates_to_recreate: function(state) { void(state); return 5000; },
6038 track_colors: function(state) { void(state); return false; },
6039 pixels_per_point: function(state) { void(state); return 15; }
6042 initialize: NETDATA.d3Initialize,
6043 create: NETDATA.d3ChartCreate,
6044 update: NETDATA.d3ChartUpdate,
6046 setSelection: undefined, // function(state, t) { void(state); return true; },
6047 clearSelection: undefined, // function(state) { void(state); return true; },
6048 toolboxPanAndZoom: null,
6051 format: function(state) { void(state); return 'json'; },
6052 options: function(state) { void(state); return ''; },
6053 legend: function(state) { void(state); return null; },
6054 autoresize: function(state) { void(state); return false; },
6055 max_updates_to_recreate: function(state) { void(state); return 5000; },
6056 track_colors: function(state) { void(state); return false; },
6057 pixels_per_point: function(state) { void(state); return 3; }
6060 initialize: NETDATA.easypiechartInitialize,
6061 create: NETDATA.easypiechartChartCreate,
6062 update: NETDATA.easypiechartChartUpdate,
6064 setSelection: NETDATA.easypiechartSetSelection,
6065 clearSelection: NETDATA.easypiechartClearSelection,
6066 toolboxPanAndZoom: null,
6069 format: function(state) { void(state); return 'array'; },
6070 options: function(state) { void(state); return 'absolute'; },
6071 legend: function(state) { void(state); return null; },
6072 autoresize: function(state) { void(state); return false; },
6073 max_updates_to_recreate: function(state) { void(state); return 5000; },
6074 track_colors: function(state) { void(state); return true; },
6075 pixels_per_point: function(state) { void(state); return 3; },
6079 initialize: NETDATA.gaugeInitialize,
6080 create: NETDATA.gaugeChartCreate,
6081 update: NETDATA.gaugeChartUpdate,
6083 setSelection: NETDATA.gaugeSetSelection,
6084 clearSelection: NETDATA.gaugeClearSelection,
6085 toolboxPanAndZoom: null,
6088 format: function(state) { void(state); return 'array'; },
6089 options: function(state) { void(state); return 'absolute'; },
6090 legend: function(state) { void(state); return null; },
6091 autoresize: function(state) { void(state); return false; },
6092 max_updates_to_recreate: function(state) { void(state); return 5000; },
6093 track_colors: function(state) { void(state); return true; },
6094 pixels_per_point: function(state) { void(state); return 3; },
6099 NETDATA.registerChartLibrary = function(library, url) {
6100 if(NETDATA.options.debug.libraries === true)
6101 console.log("registering chart library: " + library);
6103 NETDATA.chartLibraries[library].url = url;
6104 NETDATA.chartLibraries[library].initialized = true;
6105 NETDATA.chartLibraries[library].enabled = true;
6108 // ----------------------------------------------------------------------------------------------------------------
6109 // Load required JS libraries and CSS
6111 NETDATA.requiredJs = [
6113 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
6115 isAlreadyLoaded: function() {
6116 // check if bootstrap is loaded
6117 if(typeof $().emulateTransitionEnd === 'function')
6120 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6125 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
6126 isAlreadyLoaded: function() { return false; }
6130 NETDATA.requiredCSS = [
6132 url: NETDATA.themes.current.bootstrap_css,
6133 isAlreadyLoaded: function() {
6134 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
6138 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
6139 isAlreadyLoaded: function() { return false; }
6142 url: NETDATA.themes.current.dashboard_css,
6143 isAlreadyLoaded: function() { return false; }
6147 NETDATA.loadedRequiredJs = 0;
6148 NETDATA.loadRequiredJs = function(index, callback) {
6149 if(index >= NETDATA.requiredJs.length) {
6150 if(typeof callback === 'function')
6155 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6156 NETDATA.loadedRequiredJs++;
6157 NETDATA.loadRequiredJs(++index, callback);
6161 if(NETDATA.options.debug.main_loop === true)
6162 console.log('loading ' + NETDATA.requiredJs[index].url);
6165 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6169 url: NETDATA.requiredJs[index].url,
6172 xhrFields: { withCredentials: true } // required for the cookie
6175 if(NETDATA.options.debug.main_loop === true)
6176 console.log('loaded ' + NETDATA.requiredJs[index].url);
6179 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6181 .always(function() {
6182 NETDATA.loadedRequiredJs++;
6185 NETDATA.loadRequiredJs(++index, callback);
6189 NETDATA.loadRequiredJs(++index, callback);
6192 NETDATA.loadRequiredCSS = function(index) {
6193 if(index >= NETDATA.requiredCSS.length)
6196 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6197 NETDATA.loadRequiredCSS(++index);
6201 if(NETDATA.options.debug.main_loop === true)
6202 console.log('loading ' + NETDATA.requiredCSS[index].url);
6204 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6205 NETDATA.loadRequiredCSS(++index);
6209 // ----------------------------------------------------------------------------------------------------------------
6210 // Registry of netdata hosts
6213 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6214 chart_div_offset: 100, // give that space above the chart when scrolling to it
6215 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6216 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6218 ms_penalty: 0, // the time penalty of the next alarm
6219 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6220 // if alarms are shown faster than: one per 500ms
6222 notifications: false, // when true, the browser supports notifications (may not be granted though)
6223 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6224 first_notification_id: 0, // the id of the first alarm_log entry for this session
6225 // this is used to prevent CLEAR notifications for past events
6226 // notifications_shown: [],
6228 server: null, // the server to connect to for fetching alarms
6229 current: null, // the list of raised alarms - updated in the background
6230 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6232 notify: function(entry) {
6233 // console.log('alarm ' + entry.unique_id);
6235 if(entry.updated === true) {
6236 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6240 var value_string = entry.value_string;
6242 if(NETDATA.alarms.current !== null) {
6243 // get the current value_string
6244 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6245 if(typeof t !== 'undefined' && entry.status === t.status && typeof t.value_string !== 'undefined')
6246 value_string = t.value_string;
6249 var name = entry.name.replace(/_/g, ' ');
6250 var status = entry.status.toLowerCase();
6251 var title = name + ' = ' + value_string.toString();
6252 var tag = entry.alarm_id;
6253 var icon = 'images/seo-performance-128.png';
6254 var interaction = false;
6258 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6260 switch(entry.status) {
6268 case 'UNINITIALIZED':
6272 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6273 // console.log('alarm ' + entry.unique_id + ' is not current');
6276 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6277 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6280 if(entry.no_clear_notification === true) {
6281 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6284 title = name + ' back to normal (' + value_string.toString() + ')';
6285 icon = 'images/check-mark-2-128-green.png';
6286 interaction = false;
6290 if(entry.old_status === 'CRITICAL')
6291 status = 'demoted to ' + entry.status.toLowerCase();
6293 icon = 'images/alert-128-orange.png';
6294 interaction = false;
6298 if(entry.old_status === 'WARNING')
6299 status = 'escalated to ' + entry.status.toLowerCase();
6301 icon = 'images/alert-128-red.png';
6306 console.log('invalid alarm status ' + entry.status);
6311 // cleanup old notifications with the same alarm_id as this one
6312 // FIXME: it does not seem to work on any web browser!
6313 var len = NETDATA.alarms.notifications_shown.length;
6315 var n = NETDATA.alarms.notifications_shown[len];
6316 if(n.data.alarm_id === entry.alarm_id) {
6317 console.log('removing old alarm ' + n.data.unique_id);
6319 // close the notification
6322 // remove it from the array
6323 NETDATA.alarms.notifications_shown.splice(len, 1);
6324 len = NETDATA.alarms.notifications_shown.length;
6331 setTimeout(function() {
6332 // show this notification
6333 // console.log('new notification: ' + title);
6334 var n = new Notification(title, {
6335 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6337 requireInteraction: interaction,
6338 icon: NETDATA.serverDefault + icon,
6342 n.onclick = function(event) {
6343 event.preventDefault();
6344 NETDATA.alarms.onclick(event.target.data);
6348 // NETDATA.alarms.notifications_shown.push(n);
6349 // console.log(entry);
6350 }, NETDATA.alarms.ms_penalty);
6352 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6356 scrollToChart: function(chart_id) {
6357 if(typeof chart_id === 'string') {
6358 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6359 if(typeof offset !== 'undefined') {
6360 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6367 scrollToAlarm: function(alarm) {
6368 if(typeof alarm === 'object') {
6369 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6371 if(ret === true && NETDATA.options.page_is_visible === false)
6373 // 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.');
6378 notifyAll: function() {
6379 // console.log('FETCHING ALARM LOG');
6380 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6381 // console.log('ALARM LOG FETCHED');
6383 if(data === null || typeof data !== 'object') {
6384 console.log('invalid alarms log response');
6388 if(data.length === 0) {
6389 console.log('received empty alarm log');
6393 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6395 data.sort(function(a, b) {
6396 if(a.unique_id > b.unique_id) return -1;
6397 if(a.unique_id < b.unique_id) return 1;
6401 NETDATA.alarms.ms_penalty = 0;
6403 var len = data.length;
6405 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6406 NETDATA.alarms.notify(data[len]);
6409 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6412 NETDATA.alarms.last_notification_id = data[0].unique_id;
6413 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6414 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6418 check_notifications: function() {
6419 // returns true if we should fire 1+ notifications
6421 if(NETDATA.alarms.notifications !== true) {
6422 // console.log('notifications not available');
6426 if(Notification.permission !== 'granted') {
6427 // console.log('notifications not granted');
6431 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6432 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6434 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6435 // console.log('new alarms detected');
6438 //else console.log('no new alarms');
6440 // else console.log('cannot process alarms');
6445 get: function(what, callback) {
6447 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6451 'Cache-Control': 'no-cache, no-store',
6452 'Pragma': 'no-cache'
6454 xhrFields: { withCredentials: true } // required for the cookie
6456 .done(function(data) {
6457 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6458 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6460 if(typeof callback === 'function')
6461 return callback(data);
6464 NETDATA.error(415, NETDATA.alarms.server);
6466 if(typeof callback === 'function')
6467 return callback(null);
6471 update_forever: function() {
6472 NETDATA.alarms.get('active', function(data) {
6474 NETDATA.alarms.current = data;
6476 if(NETDATA.alarms.check_notifications() === true) {
6477 NETDATA.alarms.notifyAll();
6480 if (typeof NETDATA.alarms.callback === 'function') {
6481 NETDATA.alarms.callback(data);
6484 // Health monitoring is disabled on this netdata
6485 if(data.status === false) return;
6488 setTimeout(NETDATA.alarms.update_forever, 10000);
6492 get_log: function(last_id, callback) {
6493 // console.log('fetching all log after ' + last_id.toString());
6495 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6499 'Cache-Control': 'no-cache, no-store',
6500 'Pragma': 'no-cache'
6502 xhrFields: { withCredentials: true } // required for the cookie
6504 .done(function(data) {
6505 if(typeof callback === 'function')
6506 return callback(data);
6509 NETDATA.error(416, NETDATA.alarms.server);
6511 if(typeof callback === 'function')
6512 return callback(null);
6517 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6519 NETDATA.alarms.last_notification_id =
6520 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6522 if(NETDATA.alarms.onclick === null)
6523 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6525 if(netdataShowAlarms === true) {
6526 NETDATA.alarms.update_forever();
6528 if('Notification' in window) {
6529 // console.log('notifications available');
6530 NETDATA.alarms.notifications = true;
6532 if(Notification.permission === 'default')
6533 Notification.requestPermission();
6539 // ----------------------------------------------------------------------------------------------------------------
6540 // Registry of netdata hosts
6542 NETDATA.registry = {
6543 server: null, // the netdata registry server
6544 person_guid: null, // the unique ID of this browser / user
6545 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6546 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6547 machines: null, // the user's other URLs
6548 machines_array: null, // the user's other URLs in an array
6551 parsePersonUrls: function(person_urls) {
6552 // console.log(person_urls);
6553 NETDATA.registry.person_urls = person_urls;
6556 NETDATA.registry.machines = {};
6557 NETDATA.registry.machines_array = [];
6559 var apu = person_urls;
6562 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6563 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6569 accesses: apu[i][3],
6573 obj.alternate_urls.push(apu[i][1]);
6575 NETDATA.registry.machines[apu[i][0]] = obj;
6576 NETDATA.registry.machines_array.push(obj);
6579 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6581 var pu = NETDATA.registry.machines[apu[i][0]];
6582 if(pu.last_t < apu[i][2]) {
6584 pu.last_t = apu[i][2];
6585 pu.name = apu[i][4];
6587 pu.accesses += apu[i][3];
6588 pu.alternate_urls.push(apu[i][1]);
6593 if(typeof netdataRegistryCallback === 'function')
6594 netdataRegistryCallback(NETDATA.registry.machines_array);
6598 if(netdataRegistry !== true) return;
6600 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6602 NETDATA.registry.server = data.registry;
6603 NETDATA.registry.machine_guid = data.machine_guid;
6604 NETDATA.registry.hostname = data.hostname;
6606 NETDATA.registry.access(2, function (person_urls) {
6607 NETDATA.registry.parsePersonUrls(person_urls);
6614 hello: function(host, callback) {
6615 host = NETDATA.fixHost(host);
6617 // send HELLO to a netdata server:
6618 // 1. verifies the server is reachable
6619 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6621 url: host + '/api/v1/registry?action=hello',
6625 'Cache-Control': 'no-cache, no-store',
6626 'Pragma': 'no-cache'
6628 xhrFields: { withCredentials: true } // required for the cookie
6630 .done(function(data) {
6631 if(typeof data.status !== 'string' || data.status !== 'ok') {
6632 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6636 if(typeof callback === 'function')
6637 return callback(data);
6640 NETDATA.error(407, host);
6642 if(typeof callback === 'function')
6643 return callback(null);
6647 access: function(max_redirects, callback) {
6648 // send ACCESS to a netdata registry:
6649 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6650 // 2. it responds with a list of netdata servers we know
6651 // the registry identifies us using a cookie it sets the first time we access it
6652 // the registry may respond with a redirect URL to send us to another registry
6654 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),
6658 'Cache-Control': 'no-cache, no-store',
6659 'Pragma': 'no-cache'
6661 xhrFields: { withCredentials: true } // required for the cookie
6663 .done(function(data) {
6664 var redirect = null;
6665 if(typeof data.registry === 'string')
6666 redirect = data.registry;
6668 if(typeof data.status !== 'string' || data.status !== 'ok') {
6669 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6674 if(redirect !== null && max_redirects > 0) {
6675 NETDATA.registry.server = redirect;
6676 NETDATA.registry.access(max_redirects - 1, callback);
6679 if(typeof callback === 'function')
6680 return callback(null);
6684 if(typeof data.person_guid === 'string')
6685 NETDATA.registry.person_guid = data.person_guid;
6687 if(typeof callback === 'function')
6688 return callback(data.urls);
6692 NETDATA.error(410, NETDATA.registry.server);
6694 if(typeof callback === 'function')
6695 return callback(null);
6699 delete: function(delete_url, callback) {
6700 // send DELETE to a netdata registry:
6702 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),
6706 'Cache-Control': 'no-cache, no-store',
6707 'Pragma': 'no-cache'
6709 xhrFields: { withCredentials: true } // required for the cookie
6711 .done(function(data) {
6712 if(typeof data.status !== 'string' || data.status !== 'ok') {
6713 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6717 if(typeof callback === 'function')
6718 return callback(data);
6721 NETDATA.error(412, NETDATA.registry.server);
6723 if(typeof callback === 'function')
6724 return callback(null);
6728 search: function(machine_guid, callback) {
6729 // SEARCH for the URLs of a machine:
6731 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,
6735 'Cache-Control': 'no-cache, no-store',
6736 'Pragma': 'no-cache'
6738 xhrFields: { withCredentials: true } // required for the cookie
6740 .done(function(data) {
6741 if(typeof data.status !== 'string' || data.status !== 'ok') {
6742 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6746 if(typeof callback === 'function')
6747 return callback(data);
6750 NETDATA.error(418, NETDATA.registry.server);
6752 if(typeof callback === 'function')
6753 return callback(null);
6757 switch: function(new_person_guid, callback) {
6760 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,
6764 'Cache-Control': 'no-cache, no-store',
6765 'Pragma': 'no-cache'
6767 xhrFields: { withCredentials: true } // required for the cookie
6769 .done(function(data) {
6770 if(typeof data.status !== 'string' || data.status !== 'ok') {
6771 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6775 if(typeof callback === 'function')
6776 return callback(data);
6779 NETDATA.error(414, NETDATA.registry.server);
6781 if(typeof callback === 'function')
6782 return callback(null);
6787 // ----------------------------------------------------------------------------------------------------------------
6790 if(typeof netdataPrepCallback === 'function')
6791 netdataPrepCallback();
6793 NETDATA.errorReset();
6794 NETDATA.loadRequiredCSS(0);
6796 NETDATA._loadjQuery(function() {
6797 NETDATA.loadRequiredJs(0, function() {
6798 if(typeof $().emulateTransitionEnd !== 'function') {
6799 // bootstrap is not available
6800 NETDATA.options.current.show_help = false;
6803 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6804 if(NETDATA.options.debug.main_loop === true)
6805 console.log('starting chart refresh thread');
6811 })(window, document);