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 *//* 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 *//* boolean, disable charts help
36 /*global netdataShowAlarms *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs *//* 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-d5260c3.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;
221 // ----------------------------------------------------------------------------------------------------------------
222 // the defaults for all charts
224 // if the user does not specify any of these, the following will be used
226 NETDATA.chartDefaults = {
227 host: NETDATA.serverDefault, // the server to get data from
228 width: '100%', // the chart width - can be null
229 height: '100%', // the chart height - can be null
230 min_width: null, // the chart minimum width - can be null
231 library: 'dygraph', // the graphing library to use
232 method: 'average', // the grouping method
233 before: 0, // panning
234 after: -600, // panning
235 pixels_per_point: 1, // the detail of the chart
236 fill_luminance: 0.8 // luminance of colors in solit areas
239 // ----------------------------------------------------------------------------------------------------------------
243 pauseCallback: null, // a callback when we are really paused
245 pause: false, // when enabled we don't auto-refresh the charts
247 targets: null, // an array of all the state objects that are
248 // currently active (independently of their
249 // viewport visibility)
251 updated_dom: true, // when true, the DOM has been updated with
252 // new elements we have to check.
254 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
255 // rendering charts continiously.
256 // used with .current.fast_render_timeframe
258 page_is_visible: true, // when true, this page is visible
260 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
261 // auto-refresher for some time (when a chart is
262 // performing pan or zoom, we need to stop refreshing
263 // all other charts, to have the maximum speed for
264 // rendering the chart that is panned or zoomed).
265 // Used with .current.global_pan_sync_time
267 last_resized: Date.now(), // the timestamp of the last resize request
269 last_page_scroll: 0, // the timestamp the last time the page was scrolled
271 // the current profile
272 // we may have many...
274 pixels_per_point: 1, // the minimum pixels per point for all charts
275 // increase this to speed javascript up
276 // each chart library has its own limit too
277 // the max of this and the chart library is used
278 // the final is calculated every time, so a change
279 // here will have immediate effect on the next chart
282 idle_between_charts: 100, // ms - how much time to wait between chart updates
284 fast_render_timeframe: 200, // ms - render continously until this time of continious
285 // rendering has been reached
286 // this setting is used to make it render e.g. 10
287 // charts at once, sleep idle_between_charts time
288 // and continue for another 10 charts.
290 idle_between_loops: 500, // ms - if all charts have been updated, wait this
291 // time before starting again.
293 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
295 idle_lost_focus: 500, // ms - when the window does not have focus, check
296 // if focus has been regained, every this time
298 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
299 // autorefreshing of charts is paused for this amount
302 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
303 // of time before setting up synchronized selections
306 sync_selection: true, // enable or disable selection sync
308 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
310 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
312 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
314 update_only_visible: true, // enable or disable visibility management
316 parallel_refresher: true, // enable parallel refresh of charts
318 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
320 destroy_on_hide: false, // destroy charts when they are not visible
322 show_help: netdataShowHelp, // when enabled the charts will show some help
323 show_help_delay_show_ms: 500,
324 show_help_delay_hide_ms: 0,
326 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
328 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
329 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
331 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
333 smooth_plot: true, // enable smooth plot, where possible
335 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
337 color_fill_opacity_line: 1.0,
338 color_fill_opacity_area: 0.2,
339 color_fill_opacity_stacked: 0.8,
341 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
342 pan_and_zoom_factor_multiplier_control: 2.0,
343 pan_and_zoom_factor_multiplier_shift: 3.0,
344 pan_and_zoom_factor_multiplier_alt: 4.0,
346 abort_ajax_on_scroll: false, // kill pending ajax page scroll
347 async_on_scroll: false, // sync/async onscroll handler
348 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
350 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
352 setOptionCallback: function() { }
360 chart_data_url: false,
361 chart_errors: false, // FIXME: remember to set it to false before merging
369 NETDATA.statistics = {
372 refreshes_active_max: 0
376 // ----------------------------------------------------------------------------------------------------------------
377 // local storage options
379 NETDATA.localStorage = {
382 callback: {} // only used for resetting back to defaults
385 NETDATA.localStorageTested = -1;
386 NETDATA.localStorageTest = function() {
387 if(NETDATA.localStorageTested !== -1)
388 return NETDATA.localStorageTested;
390 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
393 localStorage.setItem(test, test);
394 localStorage.removeItem(test);
395 NETDATA.localStorageTested = true;
398 NETDATA.localStorageTested = false;
402 NETDATA.localStorageTested = false;
404 return NETDATA.localStorageTested;
407 NETDATA.localStorageGet = function(key, def, callback) {
410 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
411 NETDATA.localStorage.default[key.toString()] = def;
412 NETDATA.localStorage.callback[key.toString()] = callback;
415 if(NETDATA.localStorageTest() === true) {
417 // console.log('localStorage: loading "' + key.toString() + '"');
418 ret = localStorage.getItem(key.toString());
419 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
420 if(ret === null || ret === 'undefined') {
421 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(def));
426 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
427 ret = JSON.parse(ret);
428 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
432 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
437 if(typeof ret === 'undefined' || ret === 'undefined') {
438 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
442 NETDATA.localStorage.current[key.toString()] = ret;
446 NETDATA.localStorageSet = function(key, value, callback) {
447 if(typeof value === 'undefined' || value === 'undefined') {
448 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
451 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
452 NETDATA.localStorage.default[key.toString()] = value;
453 NETDATA.localStorage.current[key.toString()] = value;
454 NETDATA.localStorage.callback[key.toString()] = callback;
457 if(NETDATA.localStorageTest() === true) {
458 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
460 localStorage.setItem(key.toString(), JSON.stringify(value));
463 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
467 NETDATA.localStorage.current[key.toString()] = value;
471 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
472 var keys = Object.keys(obj);
473 var len = keys.length;
477 if(typeof obj[i] === 'object') {
478 //console.log('object ' + prefix + '.' + i.toString());
479 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
483 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
487 NETDATA.setOption = function(key, value) {
488 if(key.toString() === 'setOptionCallback') {
489 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
490 NETDATA.options.current[key.toString()] = value;
491 NETDATA.options.current.setOptionCallback();
494 else if(NETDATA.options.current[key.toString()] !== value) {
495 var name = 'options.' + key.toString();
497 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
498 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
500 //console.log(NETDATA.localStorage);
501 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
502 //console.log(NETDATA.options);
503 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
505 if(typeof NETDATA.options.current.setOptionCallback === 'function')
506 NETDATA.options.current.setOptionCallback();
512 NETDATA.getOption = function(key) {
513 return NETDATA.options.current[key.toString()];
516 // read settings from local storage
517 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
519 // always start with this option enabled.
520 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
522 NETDATA.resetOptions = function() {
523 var keys = Object.keys(NETDATA.localStorage.default);
524 var len = keys.length;
527 var a = i.split('.');
529 if(a[0] === 'options') {
530 if(a[1] === 'setOptionCallback') continue;
531 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
532 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
534 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
536 else if(a[0] === 'chart_heights') {
537 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
538 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
544 // ----------------------------------------------------------------------------------------------------------------
546 if(NETDATA.options.debug.main_loop === true)
547 console.log('welcome to NETDATA');
549 NETDATA.onresizeCallback = null;
550 NETDATA.onresize = function() {
551 NETDATA.options.last_resized = Date.now();
554 if(typeof NETDATA.onresizeCallback === 'function')
555 NETDATA.onresizeCallback();
558 NETDATA.onscroll_updater_count = 0;
559 NETDATA.onscroll_updater_running = false;
560 NETDATA.onscroll_updater_last_run = 0;
561 NETDATA.onscroll_updater_watchdog = null;
562 NETDATA.onscroll_updater_max_duration = 0;
563 NETDATA.onscroll_updater_above_threshold_count = 0;
564 NETDATA.onscroll_updater = function() {
565 NETDATA.onscroll_updater_running = true;
566 NETDATA.onscroll_updater_count++;
567 var start = Date.now();
569 var targets = NETDATA.options.targets;
570 var len = targets.length;
572 // when the user scrolls he sees that we have
573 // hidden all the not-visible charts
574 // using this little function we try to switch
575 // the charts back to visible quickly
578 if(NETDATA.options.abort_ajax_on_scroll === true) {
579 // we have to cancel pending requests too
582 if (targets[len]._updating === true) {
583 if (typeof targets[len].xhr !== 'undefined') {
584 targets[len].xhr.abort();
585 targets[len].running = false;
586 targets[len]._updating = false;
588 targets[len].isVisible();
593 // just find which chart is visible
596 targets[len].isVisible();
599 var end = Date.now();
600 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
602 if(NETDATA.options.current.async_on_scroll === false) {
603 var dt = end - start;
604 if(dt > NETDATA.onscroll_updater_max_duration) {
605 // console.log('max onscroll event handler duration increased to ' + dt);
606 NETDATA.onscroll_updater_max_duration = dt;
609 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
610 // console.log('slow: ' + dt);
611 NETDATA.onscroll_updater_above_threshold_count++;
613 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
614 NETDATA.setOption('async_on_scroll', true);
615 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
620 NETDATA.onscroll_updater_last_run = start;
621 NETDATA.onscroll_updater_running = false;
624 NETDATA.onscroll = function() {
625 // console.log('onscroll');
627 NETDATA.options.last_page_scroll = Date.now();
628 NETDATA.options.auto_refresher_stop_until = 0;
630 if(NETDATA.options.targets === null) return;
632 if(NETDATA.options.current.async_on_scroll === true) {
634 if(NETDATA.onscroll_updater_running === false) {
635 NETDATA.onscroll_updater_running = true;
636 setTimeout(NETDATA.onscroll_updater, 0);
639 if(NETDATA.onscroll_updater_watchdog !== null)
640 clearTimeout(NETDATA.onscroll_updater_watchdog);
642 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
643 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
644 // console.log('watchdog');
645 NETDATA.onscroll_updater();
648 NETDATA.onscroll_updater_watchdog = null;
654 NETDATA.onscroll_updater();
658 window.onresize = NETDATA.onresize;
659 window.onscroll = NETDATA.onscroll;
661 // ----------------------------------------------------------------------------------------------------------------
664 NETDATA.errorCodes = {
665 100: { message: "Cannot load chart library", alert: true },
666 101: { message: "Cannot load jQuery", alert: true },
667 402: { message: "Chart library not found", alert: false },
668 403: { message: "Chart library not enabled/is failed", alert: false },
669 404: { message: "Chart not found", alert: false },
670 405: { message: "Cannot download charts index from server", alert: true },
671 406: { message: "Invalid charts index downloaded from server", alert: true },
672 407: { message: "Cannot HELLO netdata server", alert: false },
673 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
674 409: { message: "Cannot ACCESS netdata registry", alert: false },
675 410: { message: "Netdata registry ACCESS failed", alert: false },
676 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
677 412: { message: "Netdata registry DELETE failed", alert: false },
678 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
679 414: { message: "Netdata registry SWITCH failed", alert: false },
680 415: { message: "Netdata alarms download failed", alert: false },
681 416: { message: "Netdata alarms log download failed", alert: false },
682 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
683 418: { message: "Netdata registry SEARCH failed", alert: false }
685 NETDATA.errorLast = {
691 NETDATA.error = function(code, msg) {
692 NETDATA.errorLast.code = code;
693 NETDATA.errorLast.message = msg;
694 NETDATA.errorLast.datetime = Date.now();
696 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
699 if(typeof netdataErrorCallback === 'function') {
700 ret = netdataErrorCallback('system', code, msg);
703 if(ret && NETDATA.errorCodes[code].alert)
704 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
707 NETDATA.errorReset = function() {
708 NETDATA.errorLast.code = 0;
709 NETDATA.errorLast.message = "You are doing fine!";
710 NETDATA.errorLast.datetime = 0;
713 // ----------------------------------------------------------------------------------------------------------------
714 // commonMin & commonMax
716 NETDATA.commonMin = {
720 get: function(state) {
721 if(typeof state.__commonMin === 'undefined') {
722 // get the commonMin setting
723 var self = $(state.element);
724 state.__commonMin = self.data('common-min') || null;
727 var min = state.data.min;
728 var name = state.__commonMin;
731 // we don't need commonMin
732 //state.log('no need for commonMin');
736 var t = this.keys[name];
737 if(typeof t === 'undefined') {
739 this.keys[name] = {};
743 var uuid = state.uuid;
744 if(typeof t[uuid] !== 'undefined') {
745 if(t[uuid] === min) {
746 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
747 return this.latest[name];
749 else if(min < this.latest[name]) {
750 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
752 this.latest[name] = min;
760 // find the common min
763 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
765 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
766 this.latest[name] = m;
771 NETDATA.commonMax = {
775 get: function(state) {
776 if(typeof state.__commonMax === 'undefined') {
777 // get the commonMax setting
778 var self = $(state.element);
779 state.__commonMax = self.data('common-max') || null;
782 var max = state.data.max;
783 var name = state.__commonMax;
786 // we don't need commonMax
787 //state.log('no need for commonMax');
791 var t = this.keys[name];
792 if(typeof t === 'undefined') {
794 this.keys[name] = {};
798 var uuid = state.uuid;
799 if(typeof t[uuid] !== 'undefined') {
800 if(t[uuid] === max) {
801 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
802 return this.latest[name];
804 else if(max > this.latest[name]) {
805 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
807 this.latest[name] = max;
815 // find the common max
818 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
820 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
821 this.latest[name] = m;
826 // ----------------------------------------------------------------------------------------------------------------
829 // When multiple charts need the same chart, we avoid downloading it
830 // multiple times (and having it in browser memory multiple time)
831 // by using this registry.
833 // Every time we download a chart definition, we save it here with .add()
834 // Then we try to get it back with .get(). If that fails, we download it.
836 NETDATA.fixHost = function(host) {
837 while(host.slice(-1) === '/')
838 host = host.substring(0, host.length - 1);
843 NETDATA.chartRegistry = {
846 fixid: function(id) {
847 return id.replace(/:/g, "_").replace(/\//g, "_");
850 add: function(host, id, data) {
851 host = this.fixid(host);
854 if(typeof this.charts[host] === 'undefined')
855 this.charts[host] = {};
857 //console.log('added ' + host + '/' + id);
858 this.charts[host][id] = data;
861 get: function(host, id) {
862 host = this.fixid(host);
865 if(typeof this.charts[host] === 'undefined')
868 if(typeof this.charts[host][id] === 'undefined')
871 //console.log('cached ' + host + '/' + id);
872 return this.charts[host][id];
875 downloadAll: function(host, callback) {
876 host = NETDATA.fixHost(host);
881 url: host + '/api/v1/charts',
884 xhrFields: { withCredentials: true } // required for the cookie
886 .done(function(data) {
888 var h = NETDATA.chartRegistry.fixid(host);
889 self.charts[h] = data.charts;
891 else NETDATA.error(406, host + '/api/v1/charts');
893 if(typeof callback === 'function')
894 return callback(data);
897 NETDATA.error(405, host + '/api/v1/charts');
899 if(typeof callback === 'function')
900 return callback(null);
905 // ----------------------------------------------------------------------------------------------------------------
906 // Global Pan and Zoom on charts
908 // Using this structure are synchronize all the charts, so that
909 // when you pan or zoom one, all others are automatically refreshed
910 // to the same timespan.
912 NETDATA.globalPanAndZoom = {
913 seq: 0, // timestamp ms
914 // every time a chart is panned or zoomed
915 // we set the timestamp here
916 // then we use it as a sequence number
917 // to find if other charts are syncronized
920 master: null, // the master chart (state), to which all others
923 force_before_ms: null, // the timespan to sync all other charts
924 force_after_ms: null,
929 setMaster: function(state, after, before) {
930 if(NETDATA.options.current.sync_pan_and_zoom === false)
933 if(this.master !== null && this.master !== state)
934 this.master.resetChart(true, true);
936 var now = Date.now();
939 this.force_after_ms = after;
940 this.force_before_ms = before;
941 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
943 if(typeof this.callback === 'function')
944 this.callback(true, after, before);
948 clearMaster: function() {
949 if(this.master !== null) {
950 var st = this.master;
957 this.force_after_ms = null;
958 this.force_before_ms = null;
959 NETDATA.options.auto_refresher_stop_until = 0;
961 if(typeof this.callback === 'function')
962 this.callback(false, 0, 0);
965 // is the given state the master of the global
966 // pan and zoom sync?
967 isMaster: function(state) {
968 return (this.master === state);
971 // are we currently have a global pan and zoom sync?
972 isActive: function() {
973 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
976 // check if a chart, other than the master
977 // needs to be refreshed, due to the global pan and zoom
978 shouldBeAutoRefreshed: function(state) {
979 if(this.master === null || this.seq === 0)
982 //if(state.needsRecreation())
985 return (!(state.tm.pan_and_zoom_seq === this.seq));
989 // ----------------------------------------------------------------------------------------------------------------
990 // dimensions selection
993 // move color assignment to dimensions, here
995 var dimensionStatus = function(parent, label, name_div, value_div, color) {
996 this.enabled = false;
997 this.parent = parent;
999 this.name_div = null;
1000 this.value_div = null;
1001 this.color = NETDATA.themes.current.foreground;
1002 this.selected = (parent.unselected_count === 0);
1004 this.setOptions(name_div, value_div, color);
1007 dimensionStatus.prototype.invalidate = function() {
1008 this.name_div = null;
1009 this.value_div = null;
1010 this.enabled = false;
1013 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1016 if(this.name_div !== name_div) {
1017 this.name_div = name_div;
1018 this.name_div.title = this.label;
1019 this.name_div.style.color = this.color;
1020 if(this.selected === false)
1021 this.name_div.className = 'netdata-legend-name not-selected';
1023 this.name_div.className = 'netdata-legend-name selected';
1026 if(this.value_div !== value_div) {
1027 this.value_div = value_div;
1028 this.value_div.title = this.label;
1029 this.value_div.style.color = this.color;
1030 if(this.selected === false)
1031 this.value_div.className = 'netdata-legend-value not-selected';
1033 this.value_div.className = 'netdata-legend-value selected';
1036 this.enabled = true;
1040 dimensionStatus.prototype.setHandler = function() {
1041 if(this.enabled === false) return;
1045 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1046 this.name_div.onclick = this.value_div.onclick = function(e) {
1048 if(ds.isSelected()) {
1050 if(e.shiftKey === true || e.ctrlKey === true) {
1051 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1054 if(ds.parent.countSelected() === 0)
1055 ds.parent.selectAll();
1058 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1059 if(ds.parent.countSelected() === 1) {
1060 ds.parent.selectAll();
1063 ds.parent.selectNone();
1069 // this is not selected
1070 if(e.shiftKey === true || e.ctrlKey === true) {
1071 // control or shift key is pressed -> select this too
1075 // no key is pressed -> select only this
1076 ds.parent.selectNone();
1081 ds.parent.state.redrawChart();
1085 dimensionStatus.prototype.select = function() {
1086 if(this.enabled === false) return;
1088 this.name_div.className = 'netdata-legend-name selected';
1089 this.value_div.className = 'netdata-legend-value selected';
1090 this.selected = true;
1093 dimensionStatus.prototype.unselect = function() {
1094 if(this.enabled === false) return;
1096 this.name_div.className = 'netdata-legend-name not-selected';
1097 this.value_div.className = 'netdata-legend-value hidden';
1098 this.selected = false;
1101 dimensionStatus.prototype.isSelected = function() {
1102 return(this.enabled === true && this.selected === true);
1105 // ----------------------------------------------------------------------------------------------------------------
1107 var dimensionsVisibility = function(state) {
1110 this.dimensions = {};
1111 this.selected_count = 0;
1112 this.unselected_count = 0;
1115 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1116 if(typeof this.dimensions[label] === 'undefined') {
1118 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1121 this.dimensions[label].setOptions(name_div, value_div, color);
1123 return this.dimensions[label];
1126 dimensionsVisibility.prototype.dimensionGet = function(label) {
1127 return this.dimensions[label];
1130 dimensionsVisibility.prototype.invalidateAll = function() {
1131 var keys = Object.keys(this.dimensions);
1132 var len = keys.length;
1134 this.dimensions[keys[len]].invalidate();
1137 dimensionsVisibility.prototype.selectAll = function() {
1138 var keys = Object.keys(this.dimensions);
1139 var len = keys.length;
1141 this.dimensions[keys[len]].select();
1144 dimensionsVisibility.prototype.countSelected = function() {
1146 var keys = Object.keys(this.dimensions);
1147 var len = keys.length;
1149 if(this.dimensions[keys[len]].isSelected()) selected++;
1154 dimensionsVisibility.prototype.selectNone = function() {
1155 var keys = Object.keys(this.dimensions);
1156 var len = keys.length;
1158 this.dimensions[keys[len]].unselect();
1161 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1163 this.selected_count = 0;
1164 this.unselected_count = 0;
1166 var len = array.length;
1168 var ds = this.dimensions[array[len]];
1169 if(typeof ds === 'undefined') {
1170 // console.log(array[i] + ' is not found');
1173 else if(ds.isSelected()) {
1175 this.selected_count++;
1179 this.unselected_count++;
1183 if(this.selected_count === 0 && this.unselected_count !== 0) {
1185 return this.selected2BooleanArray(array);
1192 // ----------------------------------------------------------------------------------------------------------------
1193 // global selection sync
1195 NETDATA.globalSelectionSync = {
1197 dont_sync_before: 0,
1202 if(this.state !== null)
1203 this.state.globalSelectionSyncStop();
1207 if(this.state !== null) {
1208 this.state.globalSelectionSyncDelay();
1213 // ----------------------------------------------------------------------------------------------------------------
1214 // Our state object, where all per-chart values are stored
1216 var chartState = function(element) {
1217 var self = $(element);
1218 this.element = element;
1221 // all private functions should use 'that', instead of 'this'
1224 /* error() - private
1225 * show an error instead of the chart
1227 var error = function(msg) {
1230 if(typeof netdataErrorCallback === 'function') {
1231 ret = netdataErrorCallback('chart', that.id, msg);
1235 that.element.innerHTML = that.id + ': ' + msg;
1236 that.enabled = false;
1237 that.current = that.pan;
1241 // GUID - a unique identifier for the chart
1242 this.uuid = NETDATA.guid();
1244 // string - the name of chart
1245 this.id = self.data('netdata');
1247 // string - the key for localStorage settings
1248 this.settings_id = self.data('id') || null;
1250 // the user given dimensions of the element
1251 this.width = self.data('width') || NETDATA.chartDefaults.width;
1252 this.height = self.data('height') || NETDATA.chartDefaults.height;
1253 this.height_original = this.height;
1255 if(this.settings_id !== null) {
1256 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1257 // this is the callback that will be called
1258 // if and when the user resets all localStorage variables
1259 // to their defaults
1261 resizeChartToHeight(height);
1265 // string - the netdata server URL, without any path
1266 this.host = self.data('host') || NETDATA.chartDefaults.host;
1268 // make sure the host does not end with /
1269 // all netdata API requests use absolute paths
1270 while(this.host.slice(-1) === '/')
1271 this.host = this.host.substring(0, this.host.length - 1);
1273 // string - the grouping method requested by the user
1274 this.method = self.data('method') || NETDATA.chartDefaults.method;
1276 // the time-range requested by the user
1277 this.after = self.data('after') || NETDATA.chartDefaults.after;
1278 this.before = self.data('before') || NETDATA.chartDefaults.before;
1280 // the pixels per point requested by the user
1281 this.pixels_per_point = self.data('pixels-per-point') || 1;
1282 this.points = self.data('points') || null;
1284 // the dimensions requested by the user
1285 this.dimensions = self.data('dimensions') || null;
1287 // the chart library requested by the user
1288 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1290 // how many retries we have made to load chart data from the server
1291 this.retries_on_data_failures = 0;
1293 // object - the chart library used
1294 this.library = null;
1298 this.colors_assigned = {};
1299 this.colors_available = null;
1301 // the element already created by the user
1302 this.element_message = null;
1304 // the element with the chart
1305 this.element_chart = null;
1307 // the element with the legend of the chart (if created by us)
1308 this.element_legend = null;
1309 this.element_legend_childs = {
1314 perfect_scroller: null, // the container to apply perfect scroller to
1318 this.chart_url = null; // string - the url to download chart info
1319 this.chart = null; // object - the chart as downloaded from the server
1321 this.title = self.data('title') || null; // the title of the chart
1322 this.units = self.data('units') || null; // the units of the chart dimensions
1323 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1324 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1326 this.running = false; // boolean - true when the chart is being refreshed now
1327 this.validated = false; // boolean - has the chart been validated?
1328 this.enabled = true; // boolean - is the chart enabled for refresh?
1329 this.paused = false; // boolean - is the chart paused for any reason?
1330 this.selected = false; // boolean - is the chart shown a selection?
1331 this.debug = false; // boolean - console.log() debug info about this chart
1333 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1334 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1335 this.requested_after = null; // milliseconds - the timestamp of the request after param
1336 this.requested_before = null; // milliseconds - the timestamp of the request before param
1337 this.requested_padding = null;
1338 this.view_after = 0;
1339 this.view_before = 0;
1341 this.value_decimal_detail = -1;
1342 var d = self.data('decimal-digits');
1343 if(typeof d === 'number') {
1344 this.value_decimal_detail = 1;
1346 this.value_decimal_detail *= 10;
1352 force_update_at: 0, // the timestamp to force the update at
1353 force_before_ms: null,
1354 force_after_ms: null
1359 force_update_at: 0, // the timestamp to force the update at
1360 force_before_ms: null,
1361 force_after_ms: null
1366 force_update_at: 0, // the timestamp to force the update at
1367 force_before_ms: null,
1368 force_after_ms: null
1371 // this is a pointer to one of the sub-classes below
1373 this.current = this.auto;
1375 // check the requested library is available
1376 // we don't initialize it here - it will be initialized when
1377 // this chart will be first used
1378 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1379 NETDATA.error(402, that.library_name);
1380 error('chart library "' + that.library_name + '" is not found');
1383 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1384 NETDATA.error(403, that.library_name);
1385 error('chart library "' + that.library_name + '" is not enabled');
1389 that.library = NETDATA.chartLibraries[that.library_name];
1391 // milliseconds - the time the last refresh took
1392 this.refresh_dt_ms = 0;
1394 // if we need to report the rendering speed
1395 // find the element that needs to be updated
1396 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1398 if(refresh_dt_element_name !== null)
1399 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1401 this.refresh_dt_element = null;
1403 this.dimensions_visibility = new dimensionsVisibility(this);
1405 this._updating = false;
1407 // ============================================================================================================
1408 // PRIVATE FUNCTIONS
1410 var createDOM = function() {
1411 if(that.enabled === false) return;
1413 if(that.element_message !== null) that.element_message.innerHTML = '';
1414 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1415 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1417 that.element.innerHTML = '';
1419 that.element_message = document.createElement('div');
1420 that.element_message.className = 'netdata-message icon hidden';
1421 that.element.appendChild(that.element_message);
1423 that.element_chart = document.createElement('div');
1424 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1425 that.element.appendChild(that.element_chart);
1427 if(that.hasLegend() === true) {
1428 that.element.className = "netdata-container-with-legend";
1429 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1431 that.element_legend = document.createElement('div');
1432 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1433 that.element.appendChild(that.element_legend);
1436 that.element.className = "netdata-container";
1437 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1439 that.element_legend = null;
1441 that.element_legend_childs.series = null;
1443 if(typeof(that.width) === 'string')
1444 $(that.element).css('width', that.width);
1445 else if(typeof(that.width) === 'number')
1446 $(that.element).css('width', that.width + 'px');
1448 if(typeof(that.library.aspect_ratio) === 'undefined') {
1449 if(typeof(that.height) === 'string')
1450 that.element.style.height = that.height;
1451 else if(typeof(that.height) === 'number')
1452 that.element.style.height = that.height.toString() + 'px';
1455 var w = that.element.offsetWidth;
1456 if(w === null || w === 0) {
1457 // the div is hidden
1458 // this will resize the chart when next viewed
1459 that.tm.last_resized = 0;
1462 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1465 if(NETDATA.chartDefaults.min_width !== null)
1466 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1468 that.tm.last_dom_created = Date.now();
1474 * initialize state variables
1475 * destroy all (possibly) created state elements
1476 * create the basic DOM for a chart
1478 var init = function() {
1479 if(that.enabled === false) return;
1481 that.paused = false;
1482 that.selected = false;
1484 that.chart_created = false; // boolean - is the library.create() been called?
1485 that.updates_counter = 0; // numeric - the number of refreshes made so far
1486 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1487 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1490 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1491 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1492 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1494 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1495 last_updated: 0, // the timestamp the chart last updated with data
1496 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1498 // Used with NETDATA.globalPanAndZoom.seq
1499 last_visible_check: 0, // the time we last checked if it is visible
1500 last_resized: 0, // the time the chart was resized
1501 last_hidden: 0, // the time the chart was hidden
1502 last_unhidden: 0, // the time the chart was unhidden
1503 last_autorefreshed: 0 // the time the chart was last refreshed
1506 that.data = null; // the last data as downloaded from the netdata server
1507 that.data_url = 'invalid://'; // string - the last url used to update the chart
1508 that.data_points = 0; // number - the number of points returned from netdata
1509 that.data_after = 0; // milliseconds - the first timestamp of the data
1510 that.data_before = 0; // milliseconds - the last timestamp of the data
1511 that.data_update_every = 0; // milliseconds - the frequency to update the data
1513 that.tm.last_initialized = Date.now();
1516 that.setMode('auto');
1519 var maxMessageFontSize = function() {
1520 var screenHeight = screen.height;
1521 var el = that.element;
1523 // normally we want a font size, as tall as the element
1524 var h = el.clientHeight;
1526 // but give it some air, 20% let's say, or 5 pixels min
1527 var lost = Math.max(h * 0.2, 5);
1530 // center the text, vertically
1531 var paddingTop = (lost - 5) / 2;
1533 // but check the width too
1534 // it should fit 10 characters in it
1535 var w = el.clientWidth / 10;
1537 paddingTop += (h - w) / 2;
1541 // and don't make it too huge
1542 // 5% of the screen size is good
1543 if(h > screenHeight / 20) {
1544 paddingTop += (h - (screenHeight / 20)) / 2;
1545 h = screenHeight / 20;
1549 that.element_message.style.fontSize = h.toString() + 'px';
1550 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1553 var showMessageIcon = function(icon) {
1554 that.element_message.innerHTML = icon;
1555 maxMessageFontSize();
1556 $(that.element_message).removeClass('hidden');
1557 that.___messageHidden___ = undefined;
1560 var hideMessage = function() {
1561 if(typeof that.___messageHidden___ === 'undefined') {
1562 that.___messageHidden___ = true;
1563 $(that.element_message).addClass('hidden');
1567 var showRendering = function() {
1569 if(that.chart !== null) {
1570 if(that.chart.chart_type === 'line')
1571 icon = '<i class="fa fa-line-chart"></i>';
1573 icon = '<i class="fa fa-area-chart"></i>';
1576 icon = '<i class="fa fa-area-chart"></i>';
1578 showMessageIcon(icon + ' netdata');
1581 var showLoading = function() {
1582 if(that.chart_created === false) {
1583 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1589 var isHidden = function() {
1590 return (typeof that.___chartIsHidden___ !== 'undefined');
1593 // hide the chart, when it is not visible - called from isVisible()
1594 var hideChart = function() {
1595 // hide it, if it is not already hidden
1596 if(isHidden() === true) return;
1598 if(that.chart_created === true) {
1599 if(NETDATA.options.current.destroy_on_hide === true) {
1600 // we should destroy it
1605 that.element_chart.style.display = 'none';
1606 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1607 that.tm.last_hidden = Date.now();
1610 // This works, but I not sure there are no corner cases somewhere
1611 // so it is commented - if the user has memory issues he can
1612 // set Destroy on Hide for all charts
1613 // that.data = null;
1617 that.___chartIsHidden___ = true;
1620 // unhide the chart, when it is visible - called from isVisible()
1621 var unhideChart = function() {
1622 if(isHidden() === false) return;
1624 that.___chartIsHidden___ = undefined;
1625 that.updates_since_last_unhide = 0;
1627 if(that.chart_created === false) {
1628 // we need to re-initialize it, to show our background
1629 // logo in bootstrap tabs, until the chart loads
1633 that.tm.last_unhidden = Date.now();
1634 that.element_chart.style.display = '';
1635 if(that.element_legend !== null) that.element_legend.style.display = '';
1641 var canBeRendered = function() {
1642 return (isHidden() === false && that.isVisible(true) === true);
1645 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1646 var callChartLibraryUpdateSafely = function(data) {
1649 if(canBeRendered() === false)
1652 if(NETDATA.options.debug.chart_errors === true)
1653 status = that.library.update(that, data);
1656 status = that.library.update(that, data);
1663 if(status === false) {
1664 error('chart failed to be updated as ' + that.library_name);
1671 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1672 var callChartLibraryCreateSafely = function(data) {
1675 if(canBeRendered() === false)
1678 if(NETDATA.options.debug.chart_errors === true)
1679 status = that.library.create(that, data);
1682 status = that.library.create(that, data);
1689 if(status === false) {
1690 error('chart failed to be created as ' + that.library_name);
1694 that.chart_created = true;
1695 that.updates_since_last_creation = 0;
1699 // ----------------------------------------------------------------------------------------------------------------
1702 // resizeChart() - private
1703 // to be called just before the chart library to make sure that
1704 // a properly sized dom is available
1705 var resizeChart = function() {
1706 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1707 if(that.chart_created === false) return;
1709 if(that.needsRecreation()) {
1712 else if(typeof that.library.resize === 'function') {
1713 that.library.resize(that);
1715 if(that.element_legend_childs.perfect_scroller !== null)
1716 Ps.update(that.element_legend_childs.perfect_scroller);
1718 maxMessageFontSize();
1721 that.tm.last_resized = Date.now();
1725 // this is the actual chart resize algorithm
1727 // - resize the entire container
1728 // - update the internal states
1729 // - resize the chart as the div changes height
1730 // - update the scrollbar of the legend
1731 var resizeChartToHeight = function(h) {
1733 that.element.style.height = h;
1735 if(that.settings_id !== null)
1736 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1738 var now = Date.now();
1739 NETDATA.options.last_page_scroll = now;
1740 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1743 that.tm.last_resized = 0;
1747 this.resizeHandler = function(e) {
1750 if(typeof this.event_resize === 'undefined'
1751 || this.event_resize.chart_original_w === 'undefined'
1752 || this.event_resize.chart_original_h === 'undefined')
1753 this.event_resize = {
1754 chart_original_w: this.element.clientWidth,
1755 chart_original_h: this.element.clientHeight,
1759 if(e.type === 'touchstart') {
1760 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1761 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1764 this.event_resize.mouse_start_x = e.clientX;
1765 this.event_resize.mouse_start_y = e.clientY;
1768 this.event_resize.chart_start_w = this.element.clientWidth;
1769 this.event_resize.chart_start_h = this.element.clientHeight;
1770 this.event_resize.chart_last_w = this.element.clientWidth;
1771 this.event_resize.chart_last_h = this.element.clientHeight;
1773 var now = Date.now();
1774 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1775 // double click / double tap event
1777 // console.dir(this.element_legend_childs.content);
1778 // console.dir(this.element_legend_childs.perfect_scroller);
1780 // the optimal height of the chart
1781 // showing the entire legend
1782 var optimal = this.event_resize.chart_last_h
1783 + this.element_legend_childs.perfect_scroller.scrollHeight
1784 - this.element_legend_childs.perfect_scroller.clientHeight;
1786 // if we are not optimal, be optimal
1787 if(this.event_resize.chart_last_h !== optimal) {
1788 // 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());
1789 resizeChartToHeight(optimal.toString() + 'px');
1792 // else if the current height is not the original/saved height
1793 // reset to the original/saved height
1794 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1795 // 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());
1796 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1799 // else if the current height is not the internal default height
1800 // reset to the internal default height
1801 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1802 // 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());
1803 resizeChartToHeight(this.height_original.toString());
1806 // else if the current height is not the firstchild's clientheight
1808 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1809 var parent_rect = this.element.getBoundingClientRect();
1810 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1811 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1813 // console.log(parent_rect);
1814 // console.log(content_rect);
1815 // console.log(wanted);
1817 // 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' );
1818 if(this.event_resize.chart_last_h !== wanted)
1819 resizeChartToHeight(wanted.toString() + 'px');
1823 this.event_resize.last = now;
1825 // process movement event
1826 document.onmousemove =
1827 document.ontouchmove =
1828 this.element_legend_childs.resize_handler.onmousemove =
1829 this.element_legend_childs.resize_handler.ontouchmove =
1834 case 'mousemove': y = e.clientY; break;
1835 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1839 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1841 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1842 resizeChartToHeight(newH.toString() + 'px');
1843 that.event_resize.chart_last_h = newH;
1848 // process end event
1849 document.onmouseup =
1850 document.ontouchend =
1851 this.element_legend_childs.resize_handler.onmouseup =
1852 this.element_legend_childs.resize_handler.ontouchend =
1854 // remove all the hooks
1855 document.onmouseup =
1856 document.onmousemove =
1857 document.ontouchmove =
1858 document.ontouchend =
1859 that.element_legend_childs.resize_handler.onmousemove =
1860 that.element_legend_childs.resize_handler.ontouchmove =
1861 that.element_legend_childs.resize_handler.onmouseout =
1862 that.element_legend_childs.resize_handler.onmouseup =
1863 that.element_legend_childs.resize_handler.ontouchend =
1866 // allow auto-refreshes
1867 NETDATA.options.auto_refresher_stop_until = 0;
1873 var noDataToShow = function() {
1874 showMessageIcon('<i class="fa fa-warning"></i> empty');
1875 that.legendUpdateDOM();
1876 that.tm.last_autorefreshed = Date.now();
1877 // that.data_update_every = 30 * 1000;
1878 //that.element_chart.style.display = 'none';
1879 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1880 //that.___chartIsHidden___ = true;
1883 // ============================================================================================================
1886 this.error = function(msg) {
1890 this.setMode = function(m) {
1891 if(this.current !== null && this.current.name === m) return;
1894 this.current = this.auto;
1895 else if(m === 'pan')
1896 this.current = this.pan;
1897 else if(m === 'zoom')
1898 this.current = this.zoom;
1900 this.current = this.auto;
1902 this.current.force_update_at = 0;
1903 this.current.force_before_ms = null;
1904 this.current.force_after_ms = null;
1906 this.tm.last_mode_switch = Date.now();
1909 // ----------------------------------------------------------------------------------------------------------------
1910 // global selection sync
1912 // prevent to global selection sync for some time
1913 this.globalSelectionSyncDelay = function(ms) {
1914 if(NETDATA.options.current.sync_selection === false)
1917 if(typeof ms === 'number')
1918 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1920 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1923 // can we globally apply selection sync?
1924 this.globalSelectionSyncAbility = function() {
1925 if(NETDATA.options.current.sync_selection === false)
1928 return (!(NETDATA.globalSelectionSync.dont_sync_before > Date.now()));
1931 this.globalSelectionSyncIsMaster = function() {
1932 return (NETDATA.globalSelectionSync.state === this);
1935 // this chart is the master of the global selection sync
1936 this.globalSelectionSyncBeMaster = function() {
1938 if(this.globalSelectionSyncIsMaster()) {
1939 if(this.debug === true)
1940 this.log('sync: I am the master already.');
1945 if(NETDATA.globalSelectionSync.state) {
1946 if(this.debug === true)
1947 this.log('sync: I am not the sync master. Resetting global sync.');
1949 this.globalSelectionSyncStop();
1952 // become the master
1953 if(this.debug === true)
1954 this.log('sync: becoming sync master.');
1956 this.selected = true;
1957 NETDATA.globalSelectionSync.state = this;
1959 // find the all slaves
1960 var targets = NETDATA.options.targets;
1961 var len = targets.length;
1963 var st = targets[len];
1966 if(this.debug === true)
1967 st.log('sync: not adding me to sync');
1969 else if(st.globalSelectionSyncIsEligible()) {
1970 if(this.debug === true)
1971 st.log('sync: adding to sync as slave');
1973 st.globalSelectionSyncBeSlave();
1977 // this.globalSelectionSyncDelay(100);
1980 // can the chart participate to the global selection sync as a slave?
1981 this.globalSelectionSyncIsEligible = function() {
1982 return (this.enabled === true
1983 && this.library !== null
1984 && typeof this.library.setSelection === 'function'
1985 && this.isVisible() === true
1986 && this.chart_created === true);
1989 // this chart becomes a slave of the global selection sync
1990 this.globalSelectionSyncBeSlave = function() {
1991 if(NETDATA.globalSelectionSync.state !== this)
1992 NETDATA.globalSelectionSync.slaves.push(this);
1995 // sync all the visible charts to the given time
1996 // this is to be called from the chart libraries
1997 this.globalSelectionSync = function(t) {
1998 if(this.globalSelectionSyncAbility() === false)
2001 if(this.globalSelectionSyncIsMaster() === false) {
2002 if(this.debug === true)
2003 this.log('sync: trying to be sync master.');
2005 this.globalSelectionSyncBeMaster();
2007 if(this.globalSelectionSyncAbility() === false)
2011 NETDATA.globalSelectionSync.last_t = t;
2012 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2017 // stop syncing all charts to the given time
2018 this.globalSelectionSyncStop = function() {
2019 if(NETDATA.globalSelectionSync.slaves.length) {
2020 if(this.debug === true)
2021 this.log('sync: cleaning up...');
2023 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2025 if(that.debug === true)
2026 st.log('sync: not adding me to sync stop');
2029 if(that.debug === true)
2030 st.log('sync: removed slave from sync');
2032 st.clearSelection();
2036 NETDATA.globalSelectionSync.last_t = 0;
2037 NETDATA.globalSelectionSync.slaves = [];
2038 NETDATA.globalSelectionSync.state = null;
2041 this.clearSelection();
2044 this.setSelection = function(t) {
2045 if(typeof this.library.setSelection === 'function')
2046 this.selected = (this.library.setSelection(this, t) === true);
2048 this.selected = true;
2050 if(this.selected === true && this.debug === true)
2051 this.log('selection set to ' + t.toString());
2053 return this.selected;
2056 this.clearSelection = function() {
2057 if(this.selected === true) {
2058 if(typeof this.library.clearSelection === 'function')
2059 this.selected = (!(this.library.clearSelection(this) === true));
2061 this.selected = false;
2063 if(this.selected === false && this.debug === true)
2064 this.log('selection cleared');
2069 return this.selected;
2072 // find if a timestamp (ms) is shown in the current chart
2073 this.timeIsVisible = function(t) {
2074 return (t >= this.data_after && t <= this.data_before);
2077 this.calculateRowForTime = function(t) {
2078 if(this.timeIsVisible(t) === false) return -1;
2079 return Math.floor((t - this.data_after) / this.data_update_every);
2082 // ----------------------------------------------------------------------------------------------------------------
2085 this.log = function(msg) {
2086 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2089 this.pauseChart = function() {
2090 if(this.paused === false) {
2091 if(this.debug === true)
2092 this.log('pauseChart()');
2098 this.unpauseChart = function() {
2099 if(this.paused === true) {
2100 if(this.debug === true)
2101 this.log('unpauseChart()');
2103 this.paused = false;
2107 this.resetChart = function(dont_clear_master, dont_update) {
2108 if(this.debug === true)
2109 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2111 if(typeof dont_clear_master === 'undefined')
2112 dont_clear_master = false;
2114 if(typeof dont_update === 'undefined')
2115 dont_update = false;
2117 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2118 if(this.debug === true)
2119 this.log('resetChart() diverting to clearMaster().');
2120 // this will call us back with master === true
2121 NETDATA.globalPanAndZoom.clearMaster();
2125 this.clearSelection();
2127 this.tm.pan_and_zoom_seq = 0;
2129 this.setMode('auto');
2130 this.current.force_update_at = 0;
2131 this.current.force_before_ms = null;
2132 this.current.force_after_ms = null;
2133 this.tm.last_autorefreshed = 0;
2134 this.paused = false;
2135 this.selected = false;
2136 this.enabled = true;
2137 // this.debug = false;
2139 // do not update the chart here
2140 // or the chart will flip-flop when it is the master
2141 // of a selection sync and another chart becomes
2144 if(dont_update !== true && this.isVisible() === true) {
2149 this.updateChartPanOrZoom = function(after, before) {
2150 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2153 if(this.debug === true)
2156 if(before < after) {
2157 if(this.debug === true)
2158 this.log(logme + 'flipped parameters, rejecting it.');
2163 if(typeof this.fixed_min_duration === 'undefined')
2164 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2166 var min_duration = this.fixed_min_duration;
2167 var current_duration = Math.round(this.view_before - this.view_after);
2169 // round the numbers
2170 after = Math.round(after);
2171 before = Math.round(before);
2173 // align them to update_every
2174 // stretching them further away
2175 after -= after % this.data_update_every;
2176 before += this.data_update_every - (before % this.data_update_every);
2178 // the final wanted duration
2179 var wanted_duration = before - after;
2181 // to allow panning, accept just a point below our minimum
2182 if((current_duration - this.data_update_every) < min_duration)
2183 min_duration = current_duration - this.data_update_every;
2185 // we do it, but we adjust to minimum size and return false
2186 // when the wanted size is below the current and the minimum
2188 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2189 if(this.debug === true)
2190 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2192 min_duration = this.fixed_min_duration;
2194 var dt = (min_duration - wanted_duration) / 2;
2197 wanted_duration = before - after;
2201 var tolerance = this.data_update_every * 2;
2202 var movement = Math.abs(before - this.view_before);
2204 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2205 if(this.debug === true)
2206 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);
2210 if(this.current.name === 'auto') {
2211 this.log(logme + 'caller called me with mode: ' + this.current.name);
2212 this.setMode('pan');
2215 if(this.debug === true)
2216 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);
2218 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2219 this.current.force_after_ms = after;
2220 this.current.force_before_ms = before;
2221 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2225 this.legendFormatValue = function(value) {
2226 if(value === null || value === 'undefined') return '-';
2227 if(typeof value !== 'number') return value;
2229 if(this.value_decimal_detail !== -1)
2230 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2232 var abs = Math.abs(value);
2233 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2234 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2235 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2236 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2237 return (Math.round(value * 10000) / 10000).toLocaleString();
2240 this.legendSetLabelValue = function(label, value) {
2241 var series = this.element_legend_childs.series[label];
2242 if(typeof series === 'undefined') return;
2243 if(series.value === null && series.user === null) return;
2246 // this slows down firefox and edge significantly
2247 // since it requires to use innerHTML(), instead of innerText()
2249 // if the value has not changed, skip DOM update
2250 //if(series.last === value) return;
2253 if(typeof value === 'number') {
2254 var v = Math.abs(value);
2255 s = r = this.legendFormatValue(value);
2257 if(typeof series.last === 'number') {
2258 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2259 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2260 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2262 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2272 series.last = value;
2276 var s = this.legendFormatValue(value);
2278 // caching: do not update the update to show the same value again
2279 if(s === series.last_shown_value) return;
2280 series.last_shown_value = s;
2282 if(series.value !== null) series.value.innerText = s;
2283 if(series.user !== null) series.user.innerText = s;
2286 this.__legendSetDateString = function(date) {
2287 if(date !== this.__last_shown_legend_date) {
2288 this.element_legend_childs.title_date.innerText = date;
2289 this.__last_shown_legend_date = date;
2293 this.__legendSetTimeString = function(time) {
2294 if(time !== this.__last_shown_legend_time) {
2295 this.element_legend_childs.title_time.innerText = time;
2296 this.__last_shown_legend_time = time;
2300 this.__legendSetUnitsString = function(units) {
2301 if(units !== this.__last_shown_legend_units) {
2302 this.element_legend_childs.title_units.innerText = units;
2303 this.__last_shown_legend_units = units;
2307 this.legendSetDate = function(ms) {
2308 if(typeof ms !== 'number') {
2309 this.legendShowUndefined();
2313 var d = new Date(ms);
2315 if(this.element_legend_childs.title_date)
2316 this.__legendSetDateString(d.toLocaleDateString());
2318 if(this.element_legend_childs.title_time)
2319 this.__legendSetTimeString(d.toLocaleTimeString());
2321 if(this.element_legend_childs.title_units)
2322 this.__legendSetUnitsString(this.units)
2325 this.legendShowUndefined = function() {
2326 if(this.element_legend_childs.title_date)
2327 this.__legendSetDateString(' ');
2329 if(this.element_legend_childs.title_time)
2330 this.__legendSetTimeString(this.chart.name);
2332 if(this.element_legend_childs.title_units)
2333 this.__legendSetUnitsString(' ');
2335 if(this.data && this.element_legend_childs.series !== null) {
2336 var labels = this.data.dimension_names;
2337 var i = labels.length;
2339 var label = labels[i];
2341 if(typeof label === 'undefined') continue;
2342 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2343 this.legendSetLabelValue(label, null);
2348 this.legendShowLatestValues = function() {
2349 if(this.chart === null) return;
2350 if(this.selected) return;
2352 if(this.data === null || this.element_legend_childs.series === null) {
2353 this.legendShowUndefined();
2357 var show_undefined = true;
2358 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2359 show_undefined = false;
2361 if(show_undefined) {
2362 this.legendShowUndefined();
2366 this.legendSetDate(this.view_before);
2368 var labels = this.data.dimension_names;
2369 var i = labels.length;
2371 var label = labels[i];
2373 if(typeof label === 'undefined') continue;
2374 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2377 this.legendSetLabelValue(label, null);
2379 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2383 this.legendReset = function() {
2384 this.legendShowLatestValues();
2387 // this should be called just ONCE per dimension per chart
2388 this._chartDimensionColor = function(label) {
2389 if(this.colors === null) this.chartColors();
2391 if(typeof this.colors_assigned[label] === 'undefined') {
2392 if(this.colors_available.length === 0) {
2393 var len = NETDATA.themes.current.colors.length;
2395 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2398 this.colors_assigned[label] = this.colors_available.shift();
2400 if(this.debug === true)
2401 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2404 if(this.debug === true)
2405 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2408 this.colors.push(this.colors_assigned[label]);
2409 return this.colors_assigned[label];
2412 this.chartColors = function() {
2413 if(this.colors !== null) return this.colors;
2416 this.colors_available = [];
2418 // add the standard colors
2419 var len = NETDATA.themes.current.colors.length;
2421 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2423 // add the user supplied colors
2424 var c = $(this.element).data('colors');
2425 // this.log('read colors: ' + c);
2426 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2427 if(typeof c !== 'string') {
2428 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2438 this.colors_available.unshift(c[len]);
2439 // this.log('adding color: ' + c[len]);
2448 this.legendUpdateDOM = function() {
2449 var needed = false, dim, keys, len, i;
2451 // check that the legend DOM is up to date for the downloaded dimensions
2452 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2453 // this.log('the legend does not have any series - requesting legend update');
2456 else if(this.data === null) {
2457 // this.log('the chart does not have any data - requesting legend update');
2460 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2464 var labels = this.data.dimension_names.toString();
2465 if(labels !== this.element_legend_childs.series.labels_key) {
2468 if(this.debug === true)
2469 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2473 if(needed === false) {
2474 // make sure colors available
2477 // do we have to update the current values?
2478 // we do this, only when the visible chart is current
2479 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2480 if(this.debug === true)
2481 this.log('chart is in latest position... updating values on legend...');
2483 //var labels = this.data.dimension_names;
2484 //var i = labels.length;
2486 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2490 if(this.colors === null) {
2491 // this is the first time we update the chart
2492 // let's assign colors to all dimensions
2493 if(this.library.track_colors() === true) {
2494 keys = Object.keys(this.chart.dimensions);
2496 for(i = 0; i < len ;i++)
2497 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2500 // we will re-generate the colors for the chart
2501 // based on the selected dimensions
2504 if(this.debug === true)
2505 this.log('updating Legend DOM');
2507 // mark all dimensions as invalid
2508 this.dimensions_visibility.invalidateAll();
2510 var genLabel = function(state, parent, dim, name, count) {
2511 var color = state._chartDimensionColor(name);
2513 var user_element = null;
2514 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2515 if(user_id === null)
2516 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2517 if(user_id !== null) {
2518 user_element = document.getElementById(user_id) || null;
2519 if (user_element === null)
2520 state.log('Cannot find element with id: ' + user_id);
2523 state.element_legend_childs.series[name] = {
2524 name: document.createElement('span'),
2525 value: document.createElement('span'),
2528 last_shown_value: null
2531 var label = state.element_legend_childs.series[name];
2533 // create the dimension visibility tracking for this label
2534 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2536 var rgb = NETDATA.colorHex2Rgb(color);
2537 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2538 + state.chart.chart_type
2539 + '" style="background-color: '
2540 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2541 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2543 var text = document.createTextNode(' ' + name);
2544 label.name.appendChild(text);
2547 parent.appendChild(document.createElement('br'));
2549 parent.appendChild(label.name);
2550 parent.appendChild(label.value);
2553 var content = document.createElement('div');
2555 if(this.hasLegend()) {
2556 this.element_legend_childs = {
2558 resize_handler: document.createElement('div'),
2559 toolbox: document.createElement('div'),
2560 toolbox_left: document.createElement('div'),
2561 toolbox_right: document.createElement('div'),
2562 toolbox_reset: document.createElement('div'),
2563 toolbox_zoomin: document.createElement('div'),
2564 toolbox_zoomout: document.createElement('div'),
2565 toolbox_volume: document.createElement('div'),
2566 title_date: document.createElement('span'),
2567 title_time: document.createElement('span'),
2568 title_units: document.createElement('span'),
2569 perfect_scroller: document.createElement('div'),
2573 this.element_legend.innerHTML = '';
2575 if(this.library.toolboxPanAndZoom !== null) {
2577 var get_pan_and_zoom_step = function(event) {
2579 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2581 else if (event.shiftKey)
2582 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2584 else if (event.altKey)
2585 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2588 return NETDATA.options.current.pan_and_zoom_factor;
2591 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2592 this.element.appendChild(this.element_legend_childs.toolbox);
2594 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2595 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2596 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2597 this.element_legend_childs.toolbox_left.onclick = function(e) {
2600 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2601 var before = that.view_before - step;
2602 var after = that.view_after - step;
2603 if(after >= that.netdata_first)
2604 that.library.toolboxPanAndZoom(that, after, before);
2606 if(NETDATA.options.current.show_help === true)
2607 $(this.element_legend_childs.toolbox_left).popover({
2612 placement: 'bottom',
2613 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2615 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>'
2619 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2620 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2621 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2622 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2624 NETDATA.resetAllCharts(that);
2626 if(NETDATA.options.current.show_help === true)
2627 $(this.element_legend_childs.toolbox_reset).popover({
2632 placement: 'bottom',
2633 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2634 title: 'Chart Reset',
2635 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>'
2638 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2639 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2640 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2641 this.element_legend_childs.toolbox_right.onclick = function(e) {
2643 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2644 var before = that.view_before + step;
2645 var after = that.view_after + step;
2646 if(before <= that.netdata_last)
2647 that.library.toolboxPanAndZoom(that, after, before);
2649 if(NETDATA.options.current.show_help === true)
2650 $(this.element_legend_childs.toolbox_right).popover({
2655 placement: 'bottom',
2656 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2658 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>'
2662 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2663 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2664 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2665 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2667 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2668 var before = that.view_before - dt;
2669 var after = that.view_after + dt;
2670 that.library.toolboxPanAndZoom(that, after, before);
2672 if(NETDATA.options.current.show_help === true)
2673 $(this.element_legend_childs.toolbox_zoomin).popover({
2678 placement: 'bottom',
2679 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2680 title: 'Chart Zoom In',
2681 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>'
2684 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2685 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2686 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2687 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2689 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);
2690 var before = that.view_before + dt;
2691 var after = that.view_after - dt;
2693 that.library.toolboxPanAndZoom(that, after, before);
2695 if(NETDATA.options.current.show_help === true)
2696 $(this.element_legend_childs.toolbox_zoomout).popover({
2701 placement: 'bottom',
2702 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2703 title: 'Chart Zoom Out',
2704 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>'
2707 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2708 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2709 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2710 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2711 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2712 //e.preventDefault();
2713 //alert('clicked toolbox_volume on ' + that.id);
2717 this.element_legend_childs.toolbox = null;
2718 this.element_legend_childs.toolbox_left = null;
2719 this.element_legend_childs.toolbox_reset = null;
2720 this.element_legend_childs.toolbox_right = null;
2721 this.element_legend_childs.toolbox_zoomin = null;
2722 this.element_legend_childs.toolbox_zoomout = null;
2723 this.element_legend_childs.toolbox_volume = null;
2726 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2727 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2728 this.element.appendChild(this.element_legend_childs.resize_handler);
2729 if(NETDATA.options.current.show_help === true)
2730 $(this.element_legend_childs.resize_handler).popover({
2735 placement: 'bottom',
2736 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2737 title: 'Chart Resize',
2738 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>'
2742 this.element_legend_childs.resize_handler.onmousedown =
2744 that.resizeHandler(e);
2748 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2749 that.resizeHandler(e);
2752 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2753 this.element_legend.appendChild(this.element_legend_childs.title_date);
2755 this.element_legend.appendChild(document.createElement('br'));
2757 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2758 this.element_legend.appendChild(this.element_legend_childs.title_time);
2760 this.element_legend.appendChild(document.createElement('br'));
2762 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2763 this.element_legend.appendChild(this.element_legend_childs.title_units);
2765 this.element_legend.appendChild(document.createElement('br'));
2767 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2768 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2770 content.className = 'netdata-legend-series-content';
2771 this.element_legend_childs.perfect_scroller.appendChild(content);
2773 if(NETDATA.options.current.show_help === true)
2774 $(content).popover({
2779 placement: 'bottom',
2780 title: 'Chart Legend',
2781 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2782 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2786 this.element_legend_childs = {
2788 resize_handler: null,
2791 toolbox_right: null,
2792 toolbox_reset: null,
2793 toolbox_zoomin: null,
2794 toolbox_zoomout: null,
2795 toolbox_volume: null,
2799 perfect_scroller: null,
2805 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2806 if(this.debug === true)
2807 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2809 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2810 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2815 keys = Object.keys(this.chart.dimensions);
2816 for(i = 0, len = keys.length; i < len ;i++) {
2818 tmp.push(this.chart.dimensions[dim].name);
2819 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2821 this.element_legend_childs.series.labels_key = tmp.toString();
2822 if(this.debug === true)
2823 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2826 // create a hidden div to be used for hidding
2827 // the original legend of the chart library
2828 var el = document.createElement('div');
2829 if(this.element_legend !== null)
2830 this.element_legend.appendChild(el);
2831 el.style.display = 'none';
2833 this.element_legend_childs.hidden = document.createElement('div');
2834 el.appendChild(this.element_legend_childs.hidden);
2836 if(this.element_legend_childs.perfect_scroller !== null) {
2837 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2839 wheelPropagation: true,
2840 swipePropagation: true,
2841 minScrollbarLength: null,
2842 maxScrollbarLength: null,
2843 useBothWheelAxes: false,
2844 suppressScrollX: true,
2845 suppressScrollY: false,
2846 scrollXMarginOffset: 0,
2847 scrollYMarginOffset: 0,
2850 Ps.update(this.element_legend_childs.perfect_scroller);
2853 this.legendShowLatestValues();
2856 this.hasLegend = function() {
2857 if(typeof this.___hasLegendCache___ !== 'undefined')
2858 return this.___hasLegendCache___;
2861 if(this.library && this.library.legend(this) === 'right-side') {
2862 var legend = $(this.element).data('legend') || 'yes';
2863 if(legend === 'yes') leg = true;
2866 this.___hasLegendCache___ = leg;
2870 this.legendWidth = function() {
2871 return (this.hasLegend())?140:0;
2874 this.legendHeight = function() {
2875 return $(this.element).height();
2878 this.chartWidth = function() {
2879 return $(this.element).width() - this.legendWidth();
2882 this.chartHeight = function() {
2883 return $(this.element).height();
2886 this.chartPixelsPerPoint = function() {
2887 // force an options provided detail
2888 var px = this.pixels_per_point;
2890 if(this.library && px < this.library.pixels_per_point(this))
2891 px = this.library.pixels_per_point(this);
2893 if(px < NETDATA.options.current.pixels_per_point)
2894 px = NETDATA.options.current.pixels_per_point;
2899 this.needsRecreation = function() {
2901 this.chart_created === true
2903 && this.library.autoresize() === false
2904 && this.tm.last_resized < NETDATA.options.last_resized
2908 this.chartURL = function() {
2909 var after, before, points_multiplier = 1;
2910 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2911 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2913 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2914 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2915 this.view_after = after * 1000;
2916 this.view_before = before * 1000;
2918 this.requested_padding = null;
2919 points_multiplier = 1;
2921 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2922 this.tm.pan_and_zoom_seq = 0;
2924 before = Math.round(this.current.force_before_ms / 1000);
2925 after = Math.round(this.current.force_after_ms / 1000);
2926 this.view_after = after * 1000;
2927 this.view_before = before * 1000;
2929 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2930 this.requested_padding = Math.round((before - after) / 2);
2931 after -= this.requested_padding;
2932 before += this.requested_padding;
2933 this.requested_padding *= 1000;
2934 points_multiplier = 2;
2937 this.current.force_before_ms = null;
2938 this.current.force_after_ms = null;
2941 this.tm.pan_and_zoom_seq = 0;
2943 before = this.before;
2945 this.view_after = after * 1000;
2946 this.view_before = before * 1000;
2948 this.requested_padding = null;
2949 points_multiplier = 1;
2952 this.requested_after = after * 1000;
2953 this.requested_before = before * 1000;
2955 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2957 // build the data URL
2958 this.data_url = this.host + this.chart.data_url;
2959 this.data_url += "&format=" + this.library.format();
2960 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2961 this.data_url += "&group=" + this.method;
2963 if(this.override_options !== null)
2964 this.data_url += "&options=" + this.override_options.toString();
2966 this.data_url += "&options=" + this.library.options(this);
2968 this.data_url += '|jsonwrap';
2970 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2971 this.data_url += '|nonzero';
2973 if(this.append_options !== null)
2974 this.data_url += '|' + this.append_options.toString();
2977 this.data_url += "&after=" + after.toString();
2980 this.data_url += "&before=" + before.toString();
2983 this.data_url += "&dimensions=" + this.dimensions;
2985 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2986 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2989 this.redrawChart = function() {
2990 if(this.data !== null)
2991 this.updateChartWithData(this.data);
2994 this.updateChartWithData = function(data) {
2995 if(this.debug === true)
2996 this.log('updateChartWithData() called.');
2998 // this may force the chart to be re-created
3002 this.updates_counter++;
3003 this.updates_since_last_unhide++;
3004 this.updates_since_last_creation++;
3006 var started = Date.now();
3008 // if the result is JSON, find the latest update-every
3009 this.data_update_every = data.view_update_every * 1000;
3010 this.data_after = data.after * 1000;
3011 this.data_before = data.before * 1000;
3012 this.netdata_first = data.first_entry * 1000;
3013 this.netdata_last = data.last_entry * 1000;
3014 this.data_points = data.points;
3017 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3018 if(this.view_after < this.data_after) {
3019 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3020 this.view_after = this.data_after;
3023 if(this.view_before > this.data_before) {
3024 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3025 this.view_before = this.data_before;
3029 this.view_after = this.data_after;
3030 this.view_before = this.data_before;
3033 if(this.debug === true) {
3034 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3036 if(this.current.force_after_ms)
3037 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3039 this.log('STATUS: forced : unset');
3041 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3042 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3043 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3044 this.log('STATUS: points : ' + (this.data_points).toString());
3047 if(this.data_points === 0) {
3052 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3053 if(this.debug === true)
3054 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3056 this.chart_created = false;
3059 // check and update the legend
3060 this.legendUpdateDOM();
3062 if(this.chart_created === true
3063 && typeof this.library.update === 'function') {
3065 if(this.debug === true)
3066 this.log('updating chart...');
3068 if(callChartLibraryUpdateSafely(data) === false)
3072 if(this.debug === true)
3073 this.log('creating chart...');
3075 if(callChartLibraryCreateSafely(data) === false)
3079 this.legendShowLatestValues();
3080 if(this.selected === true)
3081 NETDATA.globalSelectionSync.stop();
3083 // update the performance counters
3084 var now = Date.now();
3085 this.tm.last_updated = now;
3087 // don't update last_autorefreshed if this chart is
3088 // forced to be updated with global PanAndZoom
3089 if(NETDATA.globalPanAndZoom.isActive())
3090 this.tm.last_autorefreshed = 0;
3092 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3093 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3095 this.tm.last_autorefreshed = now;
3098 this.refresh_dt_ms = now - started;
3099 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3101 if(this.refresh_dt_element !== null)
3102 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3105 this.updateChart = function(callback) {
3106 if(this.debug === true)
3107 this.log('updateChart() called.');
3109 if(this._updating === true) {
3110 if(this.debug === true)
3111 this.log('I am already updating...');
3113 if(typeof callback === 'function')
3119 // due to late initialization of charts and libraries
3120 // we need to check this too
3121 if(this.enabled === false) {
3122 if(this.debug === true)
3123 this.log('I am not enabled');
3125 if(typeof callback === 'function')
3131 if(canBeRendered() === false) {
3132 if(typeof callback === 'function')
3138 if(this.chart === null)
3139 return this.getChart(function() {
3140 return that.updateChart(callback);
3143 if(this.library.initialized === false) {
3144 if(this.library.enabled === true) {
3145 return this.library.initialize(function () {
3146 return that.updateChart(callback);
3150 error('chart library "' + this.library_name + '" is not available.');
3152 if(typeof callback === 'function')
3159 this.clearSelection();
3162 if(this.debug === true)
3163 this.log('updating from ' + this.data_url);
3165 NETDATA.statistics.refreshes_total++;
3166 NETDATA.statistics.refreshes_active++;
3168 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3169 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3171 this._updating = true;
3173 this.xhr = $.ajax( {
3178 'Cache-Control': 'no-cache, no-store',
3179 'Pragma': 'no-cache'
3181 xhrFields: { withCredentials: true } // required for the cookie
3183 .done(function(data) {
3184 that.xhr = undefined;
3185 that.retries_on_data_failures = 0;
3187 if(that.debug === true)
3188 that.log('data received. updating chart.');
3190 that.updateChartWithData(data);
3192 .fail(function(msg) {
3193 that.xhr = undefined;
3195 if(msg.statusText !== 'abort') {
3196 that.retries_on_data_failures++;
3197 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3198 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3199 that.retries_on_data_failures = 0;
3200 error('data download failed for url: ' + that.data_url);
3203 that.tm.last_autorefreshed = Date.now();
3204 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3208 .always(function() {
3209 that.xhr = undefined;
3211 NETDATA.statistics.refreshes_active--;
3212 that._updating = false;
3214 if(typeof callback === 'function')
3219 this.isVisible = function(nocache) {
3220 if(typeof nocache === 'undefined')
3223 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3225 // caching - we do not evaluate the charts visibility
3226 // if the page has not been scrolled since the last check
3227 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3228 return this.___isVisible___;
3230 this.tm.last_visible_check = Date.now();
3232 var wh = window.innerHeight;
3233 var x = this.element.getBoundingClientRect();
3237 if(x.width === 0 || x.height === 0) {
3239 this.___isVisible___ = false;
3240 return this.___isVisible___;
3243 if(x.top < 0 && -x.top > x.height) {
3244 // the chart is entirely above
3245 ret = -x.top - x.height;
3247 else if(x.top > wh) {
3248 // the chart is entirely below
3252 if(ret > tolerance) {
3253 // the chart is too far
3256 this.___isVisible___ = false;
3257 return this.___isVisible___;
3260 // the chart is inside or very close
3263 this.___isVisible___ = true;
3264 return this.___isVisible___;
3268 this.isAutoRefreshable = function() {
3269 return (this.current.autorefresh);
3272 this.canBeAutoRefreshed = function() {
3273 var now = Date.now();
3275 if(this.running === true) {
3276 if(this.debug === true)
3277 this.log('I am already running');
3282 if(this.enabled === false) {
3283 if(this.debug === true)
3284 this.log('I am not enabled');
3289 if(this.library === null || this.library.enabled === false) {
3290 error('charting library "' + this.library_name + '" is not available');
3291 if(this.debug === true)
3292 this.log('My chart library ' + this.library_name + ' is not available');
3297 if(this.isVisible() === false) {
3298 if(NETDATA.options.debug.visibility === true || this.debug === true)
3299 this.log('I am not visible');
3304 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3305 if(this.debug === true)
3306 this.log('timed force update detected - allowing this update');
3308 this.current.force_update_at = 0;
3312 if(this.isAutoRefreshable() === true) {
3313 // allow the first update, even if the page is not visible
3314 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3315 if(NETDATA.options.debug.focus === true || this.debug === true)
3316 this.log('canBeAutoRefreshed(): page does not have focus');
3321 if(this.needsRecreation() === true) {
3322 if(this.debug === true)
3323 this.log('canBeAutoRefreshed(): needs re-creation.');
3328 // options valid only for autoRefresh()
3329 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3330 if(NETDATA.globalPanAndZoom.isActive()) {
3331 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3332 if(this.debug === true)
3333 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3338 if(this.debug === true)
3339 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3345 if(this.selected === true) {
3346 if(this.debug === true)
3347 this.log('canBeAutoRefreshed(): I have a selection in place.');
3352 if(this.paused === true) {
3353 if(this.debug === true)
3354 this.log('canBeAutoRefreshed(): I am paused.');
3359 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3360 if(this.debug === true)
3361 this.log('canBeAutoRefreshed(): It is time to update me.');
3371 this.autoRefresh = function(callback) {
3372 if(this.canBeAutoRefreshed() === true && this.running === false) {
3375 state.running = true;
3376 state.updateChart(function() {
3377 state.running = false;
3379 if(typeof callback !== 'undefined')
3384 if(typeof callback !== 'undefined')
3389 this._defaultsFromDownloadedChart = function(chart) {
3391 this.chart_url = chart.url;
3392 this.data_update_every = chart.update_every * 1000;
3393 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3394 this.tm.last_info_downloaded = Date.now();
3396 if(this.title === null)
3397 this.title = chart.title;
3399 if(this.units === null)
3400 this.units = chart.units;
3403 // fetch the chart description from the netdata server
3404 this.getChart = function(callback) {
3405 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3407 this._defaultsFromDownloadedChart(this.chart);
3409 if(typeof callback === 'function')
3413 this.chart_url = "/api/v1/chart?chart=" + this.id;
3415 if(this.debug === true)
3416 this.log('downloading ' + this.chart_url);
3419 url: this.host + this.chart_url,
3422 xhrFields: { withCredentials: true } // required for the cookie
3424 .done(function(chart) {
3425 chart.url = that.chart_url;
3426 that._defaultsFromDownloadedChart(chart);
3427 NETDATA.chartRegistry.add(that.host, that.id, chart);
3430 NETDATA.error(404, that.chart_url);
3431 error('chart not found on url "' + that.chart_url + '"');
3433 .always(function() {
3434 if(typeof callback === 'function')
3440 // ============================================================================================================
3446 NETDATA.resetAllCharts = function(state) {
3447 // first clear the global selection sync
3448 // to make sure no chart is in selected state
3449 state.globalSelectionSyncStop();
3451 // there are 2 possibilities here
3452 // a. state is the global Pan and Zoom master
3453 // b. state is not the global Pan and Zoom master
3455 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3458 // clear the global Pan and Zoom
3459 // this will also refresh the master
3460 // and unblock any charts currently mirroring the master
3461 NETDATA.globalPanAndZoom.clearMaster();
3463 // if we were not the master, reset our status too
3464 // this is required because most probably the mouse
3465 // is over this chart, blocking it from auto-refreshing
3466 if(master === false && (state.paused === true || state.selected === true))
3470 // get or create a chart state, given a DOM element
3471 NETDATA.chartState = function(element) {
3472 var state = $(element).data('netdata-state-object') || null;
3473 if(state === null) {
3474 state = new chartState(element);
3475 $(element).data('netdata-state-object', state);
3480 // ----------------------------------------------------------------------------------------------------------------
3481 // Library functions
3483 // Load a script without jquery
3484 // This is used to load jquery - after it is loaded, we use jquery
3485 NETDATA._loadjQuery = function(callback) {
3486 if(typeof jQuery === 'undefined') {
3487 if(NETDATA.options.debug.main_loop === true)
3488 console.log('loading ' + NETDATA.jQuery);
3490 var script = document.createElement('script');
3491 script.type = 'text/javascript';
3492 script.async = true;
3493 script.src = NETDATA.jQuery;
3495 // script.onabort = onError;
3496 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3497 if(typeof callback === "function")
3498 script.onload = callback;
3500 var s = document.getElementsByTagName('script')[0];
3501 s.parentNode.insertBefore(script, s);
3503 else if(typeof callback === "function")
3507 NETDATA._loadCSS = function(filename) {
3508 // don't use jQuery here
3509 // styles are loaded before jQuery
3510 // to eliminate showing an unstyled page to the user
3512 var fileref = document.createElement("link");
3513 fileref.setAttribute("rel", "stylesheet");
3514 fileref.setAttribute("type", "text/css");
3515 fileref.setAttribute("href", filename);
3517 if (typeof fileref !== 'undefined')
3518 document.getElementsByTagName("head")[0].appendChild(fileref);
3521 NETDATA.colorHex2Rgb = function(hex) {
3522 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3523 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3524 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3525 return r + r + g + g + b + b;
3528 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3530 r: parseInt(result[1], 16),
3531 g: parseInt(result[2], 16),
3532 b: parseInt(result[3], 16)
3536 NETDATA.colorLuminance = function(hex, lum) {
3537 // validate hex string
3538 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3540 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3544 // convert to decimal and change luminosity
3545 var rgb = "#", c, i;
3546 for (i = 0; i < 3; i++) {
3547 c = parseInt(hex.substr(i*2,2), 16);
3548 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3549 rgb += ("00"+c).substr(c.length);
3555 NETDATA.guid = function() {
3557 return Math.floor((1 + Math.random()) * 0x10000)
3562 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3565 NETDATA.zeropad = function(x) {
3566 if(x > -10 && x < 10) return '0' + x.toString();
3567 else return x.toString();
3570 // user function to signal us the DOM has been
3572 NETDATA.updatedDom = function() {
3573 NETDATA.options.updated_dom = true;
3576 NETDATA.ready = function(callback) {
3577 NETDATA.options.pauseCallback = callback;
3580 NETDATA.pause = function(callback) {
3581 if(typeof callback === 'function') {
3582 if (NETDATA.options.pause === true)
3585 NETDATA.options.pauseCallback = callback;
3589 NETDATA.unpause = function() {
3590 NETDATA.options.pauseCallback = null;
3591 NETDATA.options.updated_dom = true;
3592 NETDATA.options.pause = false;
3595 // ----------------------------------------------------------------------------------------------------------------
3597 // this is purely sequential charts refresher
3598 // it is meant to be autonomous
3599 NETDATA.chartRefresherNoParallel = function(index) {
3600 if(NETDATA.options.debug.main_loop === true)
3601 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3603 if(NETDATA.options.updated_dom === true) {
3604 // the dom has been updated
3605 // get the dom parts again
3606 NETDATA.parseDom(NETDATA.chartRefresher);
3609 if(index >= NETDATA.options.targets.length) {
3610 if(NETDATA.options.debug.main_loop === true)
3611 console.log('waiting to restart main loop...');
3613 NETDATA.options.auto_refresher_fast_weight = 0;
3615 setTimeout(function() {
3616 NETDATA.chartRefresher();
3617 }, NETDATA.options.current.idle_between_loops);
3620 var state = NETDATA.options.targets[index];
3622 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3623 if(NETDATA.options.debug.main_loop === true)
3624 console.log('fast rendering...');
3626 state.autoRefresh(function() {
3627 NETDATA.chartRefresherNoParallel(++index);
3631 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3632 NETDATA.options.auto_refresher_fast_weight = 0;
3634 setTimeout(function() {
3635 state.autoRefresh(function() {
3636 NETDATA.chartRefresherNoParallel(++index);
3638 }, NETDATA.options.current.idle_between_charts);
3643 NETDATA.chartRefresherWaitTime = function() {
3644 return NETDATA.options.current.idle_parallel_loops;
3647 // the default refresher
3648 NETDATA.chartRefresher = function() {
3649 // console.log('auto-refresher...');
3651 if(NETDATA.options.pause === true) {
3652 // console.log('auto-refresher is paused');
3653 setTimeout(NETDATA.chartRefresher,
3654 NETDATA.chartRefresherWaitTime());
3658 if(typeof NETDATA.options.pauseCallback === 'function') {
3659 // console.log('auto-refresher is calling pauseCallback');
3660 NETDATA.options.pause = true;
3661 NETDATA.options.pauseCallback();
3662 NETDATA.chartRefresher();
3666 if(NETDATA.options.current.parallel_refresher === false) {
3667 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3668 NETDATA.chartRefresherNoParallel(0);
3672 if(NETDATA.options.updated_dom === true) {
3673 // the dom has been updated
3674 // get the dom parts again
3675 // console.log('auto-refresher is calling parseDom()');
3676 NETDATA.parseDom(NETDATA.chartRefresher);
3681 var targets = NETDATA.options.targets;
3682 var len = targets.length;
3685 state = targets[len];
3686 if(state.isVisible() === false || state.running === true)
3689 if(state.library.initialized === false) {
3690 if(state.library.enabled === true) {
3691 state.library.initialize(NETDATA.chartRefresher);
3695 state.error('chart library "' + state.library_name + '" is not enabled.');
3699 parallel.unshift(state);
3702 if(parallel.length > 0) {
3703 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3704 // this will execute the jobs in parallel
3705 $(parallel).each(function() {
3710 // console.log('auto-refresher nothing to do');
3713 // run the next refresh iteration
3714 setTimeout(NETDATA.chartRefresher,
3715 NETDATA.chartRefresherWaitTime());
3718 NETDATA.parseDom = function(callback) {
3719 NETDATA.options.last_page_scroll = Date.now();
3720 NETDATA.options.updated_dom = false;
3722 var targets = $('div[data-netdata]'); //.filter(':visible');
3724 if(NETDATA.options.debug.main_loop === true)
3725 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3727 NETDATA.options.targets = [];
3728 var len = targets.length;
3730 // the initialization will take care of sizing
3731 // and the "loading..." message
3732 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3735 if(typeof callback === 'function')
3739 // this is the main function - where everything starts
3740 NETDATA.start = function() {
3741 // this should be called only once
3743 NETDATA.options.page_is_visible = true;
3745 $(window).blur(function() {
3746 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3747 NETDATA.options.page_is_visible = false;
3748 if(NETDATA.options.debug.focus === true)
3749 console.log('Lost Focus!');
3753 $(window).focus(function() {
3754 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3755 NETDATA.options.page_is_visible = true;
3756 if(NETDATA.options.debug.focus === true)
3757 console.log('Focus restored!');
3761 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3762 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3763 NETDATA.options.page_is_visible = false;
3764 if(NETDATA.options.debug.focus === true)
3765 console.log('Document has no focus!');
3769 // bootstrap tab switching
3770 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3772 // bootstrap modal switching
3773 var $modal = $('.modal');
3774 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3775 $modal.on('shown.bs.modal', NETDATA.onscroll);
3777 // bootstrap collapse switching
3778 var $collapse = $('.collapse');
3779 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3780 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3782 NETDATA.parseDom(NETDATA.chartRefresher);
3784 // Alarms initialization
3785 setTimeout(NETDATA.alarms.init, 1000);
3787 // Registry initialization
3788 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3790 if(typeof netdataCallback === 'function')
3794 // ----------------------------------------------------------------------------------------------------------------
3797 NETDATA.peityInitialize = function(callback) {
3798 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3800 url: NETDATA.peity_js,
3803 xhrFields: { withCredentials: true } // required for the cookie
3806 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3809 NETDATA.chartLibraries.peity.enabled = false;
3810 NETDATA.error(100, NETDATA.peity_js);
3812 .always(function() {
3813 if(typeof callback === "function")
3818 NETDATA.chartLibraries.peity.enabled = false;
3819 if(typeof callback === "function")
3824 NETDATA.peityChartUpdate = function(state, data) {
3825 state.peity_instance.innerHTML = data.result;
3827 if(state.peity_options.stroke !== state.chartColors()[0]) {
3828 state.peity_options.stroke = state.chartColors()[0];
3829 if(state.chart.chart_type === 'line')
3830 state.peity_options.fill = NETDATA.themes.current.background;
3832 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3835 $(state.peity_instance).peity('line', state.peity_options);
3839 NETDATA.peityChartCreate = function(state, data) {
3840 state.peity_instance = document.createElement('div');
3841 state.element_chart.appendChild(state.peity_instance);
3843 var self = $(state.element);
3844 state.peity_options = {
3845 stroke: NETDATA.themes.current.foreground,
3846 strokeWidth: self.data('peity-strokewidth') || 1,
3847 width: state.chartWidth(),
3848 height: state.chartHeight(),
3849 fill: NETDATA.themes.current.foreground
3852 NETDATA.peityChartUpdate(state, data);
3856 // ----------------------------------------------------------------------------------------------------------------
3859 NETDATA.sparklineInitialize = function(callback) {
3860 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3862 url: NETDATA.sparkline_js,
3865 xhrFields: { withCredentials: true } // required for the cookie
3868 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3871 NETDATA.chartLibraries.sparkline.enabled = false;
3872 NETDATA.error(100, NETDATA.sparkline_js);
3874 .always(function() {
3875 if(typeof callback === "function")
3880 NETDATA.chartLibraries.sparkline.enabled = false;
3881 if(typeof callback === "function")
3886 NETDATA.sparklineChartUpdate = function(state, data) {
3887 state.sparkline_options.width = state.chartWidth();
3888 state.sparkline_options.height = state.chartHeight();
3890 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3894 NETDATA.sparklineChartCreate = function(state, data) {
3895 var self = $(state.element);
3896 var type = self.data('sparkline-type') || 'line';
3897 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3898 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3899 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3900 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3901 var composite = self.data('sparkline-composite') || undefined;
3902 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3903 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3904 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3905 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3906 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3907 var spotColor = self.data('sparkline-spotcolor') || undefined;
3908 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3909 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3910 var spotRadius = self.data('sparkline-spotradius') || undefined;
3911 var valueSpots = self.data('sparkline-valuespots') || undefined;
3912 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3913 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3914 var lineWidth = self.data('sparkline-linewidth') || undefined;
3915 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3916 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3917 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3918 var xvalues = self.data('sparkline-xvalues') || undefined;
3919 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3920 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3921 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3922 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3923 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3924 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3925 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3926 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3927 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3928 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3929 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3930 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3931 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3932 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3933 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3934 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3935 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3936 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3937 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3938 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3939 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3940 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3942 if(spotColor === 'disable') spotColor='';
3943 if(minSpotColor === 'disable') minSpotColor='';
3944 if(maxSpotColor === 'disable') maxSpotColor='';
3946 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3948 state.sparkline_options = {
3950 lineColor: lineColor,
3951 fillColor: fillColor,
3952 chartRangeMin: chartRangeMin,
3953 chartRangeMax: chartRangeMax,
3954 composite: composite,
3955 enableTagOptions: enableTagOptions,
3956 tagOptionPrefix: tagOptionPrefix,
3957 tagValuesAttribute: tagValuesAttribute,
3958 disableHiddenCheck: disableHiddenCheck,
3959 defaultPixelsPerValue: defaultPixelsPerValue,
3960 spotColor: spotColor,
3961 minSpotColor: minSpotColor,
3962 maxSpotColor: maxSpotColor,
3963 spotRadius: spotRadius,
3964 valueSpots: valueSpots,
3965 highlightSpotColor: highlightSpotColor,
3966 highlightLineColor: highlightLineColor,
3967 lineWidth: lineWidth,
3968 normalRangeMin: normalRangeMin,
3969 normalRangeMax: normalRangeMax,
3970 drawNormalOnTop: drawNormalOnTop,
3972 chartRangeClip: chartRangeClip,
3973 chartRangeMinX: chartRangeMinX,
3974 chartRangeMaxX: chartRangeMaxX,
3975 disableInteraction: disableInteraction,
3976 disableTooltips: disableTooltips,
3977 disableHighlight: disableHighlight,
3978 highlightLighten: highlightLighten,
3979 highlightColor: highlightColor,
3980 tooltipContainer: tooltipContainer,
3981 tooltipClassname: tooltipClassname,
3982 tooltipChartTitle: state.title,
3983 tooltipFormat: tooltipFormat,
3984 tooltipPrefix: tooltipPrefix,
3985 tooltipSuffix: tooltipSuffix,
3986 tooltipSkipNull: tooltipSkipNull,
3987 tooltipValueLookups: tooltipValueLookups,
3988 tooltipFormatFieldlist: tooltipFormatFieldlist,
3989 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3990 numberFormatter: numberFormatter,
3991 numberDigitGroupSep: numberDigitGroupSep,
3992 numberDecimalMark: numberDecimalMark,
3993 numberDigitGroupCount: numberDigitGroupCount,
3994 animatedZooms: animatedZooms,
3995 width: state.chartWidth(),
3996 height: state.chartHeight()
3999 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4003 // ----------------------------------------------------------------------------------------------------------------
4010 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4011 if(after < state.netdata_first)
4012 after = state.netdata_first;
4014 if(before > state.netdata_last)
4015 before = state.netdata_last;
4017 state.setMode('zoom');
4018 state.globalSelectionSyncStop();
4019 state.globalSelectionSyncDelay();
4020 state.dygraph_user_action = true;
4021 state.dygraph_force_zoom = true;
4022 state.updateChartPanOrZoom(after, before);
4023 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4026 NETDATA.dygraphSetSelection = function(state, t) {
4027 if(typeof state.dygraph_instance !== 'undefined') {
4028 var r = state.calculateRowForTime(t);
4030 state.dygraph_instance.setSelection(r);
4032 state.dygraph_instance.clearSelection();
4033 state.legendShowUndefined();
4040 NETDATA.dygraphClearSelection = function(state) {
4041 if(typeof state.dygraph_instance !== 'undefined') {
4042 state.dygraph_instance.clearSelection();
4047 NETDATA.dygraphSmoothInitialize = function(callback) {
4049 url: NETDATA.dygraph_smooth_js,
4052 xhrFields: { withCredentials: true } // required for the cookie
4055 NETDATA.dygraph.smooth = true;
4056 smoothPlotter.smoothing = 0.3;
4059 NETDATA.dygraph.smooth = false;
4061 .always(function() {
4062 if(typeof callback === "function")
4067 NETDATA.dygraphInitialize = function(callback) {
4068 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4070 url: NETDATA.dygraph_js,
4073 xhrFields: { withCredentials: true } // required for the cookie
4076 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4079 NETDATA.chartLibraries.dygraph.enabled = false;
4080 NETDATA.error(100, NETDATA.dygraph_js);
4082 .always(function() {
4083 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4084 NETDATA.dygraphSmoothInitialize(callback);
4085 else if(typeof callback === "function")
4090 NETDATA.chartLibraries.dygraph.enabled = false;
4091 if(typeof callback === "function")
4096 NETDATA.dygraphChartUpdate = function(state, data) {
4097 var dygraph = state.dygraph_instance;
4099 if(typeof dygraph === 'undefined')
4100 return NETDATA.dygraphChartCreate(state, data);
4102 // when the chart is not visible, and hidden
4103 // if there is a window resize, dygraph detects
4104 // its element size as 0x0.
4105 // this will make it re-appear properly
4107 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4111 file: data.result.data,
4112 colors: state.chartColors(),
4113 labels: data.result.labels,
4114 labelsDivWidth: state.chartWidth() - 70,
4115 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4118 if(state.dygraph_force_zoom === true) {
4119 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4120 state.log('dygraphChartUpdate() forced zoom update');
4122 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4123 options.isZoomedIgnoreProgrammaticZoom = true;
4124 state.dygraph_force_zoom = false;
4126 else if(state.current.name !== 'auto') {
4127 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4128 state.log('dygraphChartUpdate() loose update');
4131 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4132 state.log('dygraphChartUpdate() strict update');
4134 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4135 options.isZoomedIgnoreProgrammaticZoom = true;
4138 options.valueRange = state.dygraph_options.valueRange;
4140 var oldMax = null, oldMin = null;
4141 if(state.__commonMin !== null) {
4142 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4143 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4145 if(state.__commonMax !== null) {
4146 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4147 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4150 if(state.dygraph_smooth_eligible === true) {
4151 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4152 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4153 NETDATA.dygraphChartCreate(state, data);
4158 dygraph.updateOptions(options);
4161 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4162 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4163 options.valueRange[0] = NETDATA.commonMin.get(state);
4166 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4167 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4168 options.valueRange[1] = NETDATA.commonMax.get(state);
4172 if(redraw === true) {
4173 // state.log('forcing redraw to adapt to common- min/max');
4174 dygraph.updateOptions(options);
4177 state.dygraph_last_rendered = Date.now();
4181 NETDATA.dygraphChartCreate = function(state, data) {
4182 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4183 state.log('dygraphChartCreate()');
4185 var self = $(state.element);
4187 var chart_type = state.chart.chart_type;
4188 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4189 chart_type = self.data('dygraph-type') || chart_type;
4191 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state));
4192 smooth = self.data('dygraph-smooth') || smooth;
4194 if(NETDATA.dygraph.smooth === false)
4197 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7);
4198 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4200 state.dygraph_options = {
4201 colors: self.data('dygraph-colors') || state.chartColors(),
4203 // leave a few pixels empty on the right of the chart
4204 rightGap: self.data('dygraph-rightgap') || 5,
4205 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4206 showRoller: self.data('dygraph-showroller') || false,
4208 title: self.data('dygraph-title') || state.title,
4209 titleHeight: self.data('dygraph-titleheight') || 19,
4211 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4212 labels: data.result.labels,
4213 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4214 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4215 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4216 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4217 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4220 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4221 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4223 includeZero: self.data('dygraph-includezero') || (chart_type === 'stacked'),
4224 xRangePad: self.data('dygraph-xrangepad') || 0,
4225 yRangePad: self.data('dygraph-yrangepad') || 1,
4227 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4229 ylabel: state.units,
4230 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4232 // the function to plot the chart
4235 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4236 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4237 strokePattern: self.data('dygraph-strokepattern') || undefined,
4239 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4240 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4241 drawPoints: self.data('dygraph-drawpoints') || false,
4243 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4244 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4246 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4247 pointSize: self.data('dygraph-pointsize') || 1,
4249 // enabling this makes the chart with little square lines
4250 stepPlot: self.data('dygraph-stepplot') || false,
4252 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4253 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4254 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4256 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked'),
4257 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4258 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked'),
4259 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4261 drawAxis: self.data('dygraph-drawaxis') || true,
4262 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4263 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4264 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4266 drawGrid: self.data('dygraph-drawgrid') || true,
4267 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4268 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4269 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4271 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4272 sigFigs: self.data('dygraph-sigfigs') || null,
4273 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4274 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4276 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4277 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4278 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4280 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4281 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4285 ticker: Dygraph.dateTicker,
4286 axisLabelFormatter: function (d, gran) {
4288 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4290 valueFormatter: function (ms) {
4292 //var d = new Date(ms);
4293 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4295 // no need to return anything here
4302 valueFormatter: function (x) {
4303 // we format legends with the state object
4304 // no need to do anything here
4305 // return (Math.round(x*100) / 100).toLocaleString();
4306 // return state.legendFormatValue(x);
4311 legendFormatter: function(data) {
4312 var elements = state.element_legend_childs;
4314 // if the hidden div is not there
4315 // we are not managing the legend
4316 if(elements.hidden === null) return;
4318 if (typeof data.x !== 'undefined') {
4319 state.legendSetDate(data.x);
4320 var i = data.series.length;
4322 var series = data.series[i];
4323 if(series.isVisible === true)
4324 state.legendSetLabelValue(series.label, series.y);
4326 state.legendSetLabelValue(series.label, null);
4332 drawCallback: function(dygraph, is_initial) {
4333 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4334 state.dygraph_user_action = false;
4336 var x_range = dygraph.xAxisRange();
4337 var after = Math.round(x_range[0]);
4338 var before = Math.round(x_range[1]);
4340 if(NETDATA.options.debug.dygraph === true)
4341 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4343 if(before <= state.netdata_last && after >= state.netdata_first)
4344 state.updateChartPanOrZoom(after, before);
4347 zoomCallback: function(minDate, maxDate, yRanges) {
4350 if(NETDATA.options.debug.dygraph === true)
4351 state.log('dygraphZoomCallback()');
4353 state.globalSelectionSyncStop();
4354 state.globalSelectionSyncDelay();
4355 state.setMode('zoom');
4357 // refresh it to the greatest possible zoom level
4358 state.dygraph_user_action = true;
4359 state.dygraph_force_zoom = true;
4360 state.updateChartPanOrZoom(minDate, maxDate);
4362 highlightCallback: function(event, x, points, row, seriesName) {
4365 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4366 state.log('dygraphHighlightCallback()');
4370 // there is a bug in dygraph when the chart is zoomed enough
4371 // the time it thinks is selected is wrong
4372 // here we calculate the time t based on the row number selected
4374 var t = state.data_after + row * state.data_update_every;
4375 // 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);
4377 state.globalSelectionSync(x);
4379 // fix legend zIndex using the internal structures of dygraph legend module
4380 // this works, but it is a hack!
4381 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4383 unhighlightCallback: function(event) {
4386 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4387 state.log('dygraphUnhighlightCallback()');
4389 state.unpauseChart();
4390 state.globalSelectionSyncStop();
4392 interactionModel : {
4393 mousedown: function(event, dygraph, context) {
4394 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4395 state.log('interactionModel.mousedown()');
4397 state.dygraph_user_action = true;
4398 state.globalSelectionSyncStop();
4400 if(NETDATA.options.debug.dygraph === true)
4401 state.log('dygraphMouseDown()');
4403 // Right-click should not initiate a zoom.
4404 if(event.button && event.button === 2) return;
4406 context.initializeMouseDown(event, dygraph, context);
4408 if(event.button && event.button === 1) {
4409 if (event.altKey || event.shiftKey) {
4410 state.setMode('pan');
4411 state.globalSelectionSyncDelay();
4412 Dygraph.startPan(event, dygraph, context);
4415 state.setMode('zoom');
4416 state.globalSelectionSyncDelay();
4417 Dygraph.startZoom(event, dygraph, context);
4421 if (event.altKey || event.shiftKey) {
4422 state.setMode('zoom');
4423 state.globalSelectionSyncDelay();
4424 Dygraph.startZoom(event, dygraph, context);
4427 state.setMode('pan');
4428 state.globalSelectionSyncDelay();
4429 Dygraph.startPan(event, dygraph, context);
4433 mousemove: function(event, dygraph, context) {
4434 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4435 state.log('interactionModel.mousemove()');
4437 if(context.isPanning) {
4438 state.dygraph_user_action = true;
4439 state.globalSelectionSyncStop();
4440 state.globalSelectionSyncDelay();
4441 state.setMode('pan');
4442 context.is2DPan = false;
4443 Dygraph.movePan(event, dygraph, context);
4445 else if(context.isZooming) {
4446 state.dygraph_user_action = true;
4447 state.globalSelectionSyncStop();
4448 state.globalSelectionSyncDelay();
4449 state.setMode('zoom');
4450 Dygraph.moveZoom(event, dygraph, context);
4453 mouseup: function(event, dygraph, context) {
4454 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4455 state.log('interactionModel.mouseup()');
4457 if (context.isPanning) {
4458 state.dygraph_user_action = true;
4459 state.globalSelectionSyncDelay();
4460 Dygraph.endPan(event, dygraph, context);
4462 else if (context.isZooming) {
4463 state.dygraph_user_action = true;
4464 state.globalSelectionSyncDelay();
4465 Dygraph.endZoom(event, dygraph, context);
4468 click: function(event, dygraph, context) {
4472 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4473 state.log('interactionModel.click()');
4475 event.preventDefault();
4477 dblclick: function(event, dygraph, context) {
4482 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4483 state.log('interactionModel.dblclick()');
4484 NETDATA.resetAllCharts(state);
4486 wheel: function(event, dygraph, context) {
4489 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4490 state.log('interactionModel.wheel()');
4492 // Take the offset of a mouse event on the dygraph canvas and
4493 // convert it to a pair of percentages from the bottom left.
4494 // (Not top left, bottom is where the lower value is.)
4495 function offsetToPercentage(g, offsetX, offsetY) {
4496 // This is calculating the pixel offset of the leftmost date.
4497 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4498 var yar0 = g.yAxisRange(0);
4500 // This is calculating the pixel of the higest value. (Top pixel)
4501 var yOffset = g.toDomCoords(null, yar0[1])[1];
4503 // x y w and h are relative to the corner of the drawing area,
4504 // so that the upper corner of the drawing area is (0, 0).
4505 var x = offsetX - xOffset;
4506 var y = offsetY - yOffset;
4508 // This is computing the rightmost pixel, effectively defining the
4510 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4512 // This is computing the lowest pixel, effectively defining the height.
4513 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4515 // Percentage from the left.
4516 var xPct = w === 0 ? 0 : (x / w);
4517 // Percentage from the top.
4518 var yPct = h === 0 ? 0 : (y / h);
4520 // The (1-) part below changes it from "% distance down from the top"
4521 // to "% distance up from the bottom".
4522 return [xPct, (1-yPct)];
4525 // Adjusts [x, y] toward each other by zoomInPercentage%
4526 // Split it so the left/bottom axis gets xBias/yBias of that change and
4527 // tight/top gets (1-xBias)/(1-yBias) of that change.
4529 // If a bias is missing it splits it down the middle.
4530 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4531 xBias = xBias || 0.5;
4532 yBias = yBias || 0.5;
4534 function adjustAxis(axis, zoomInPercentage, bias) {
4535 var delta = axis[1] - axis[0];
4536 var increment = delta * zoomInPercentage;
4537 var foo = [increment * bias, increment * (1-bias)];
4539 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4542 var yAxes = g.yAxisRanges();
4544 for (var i = 0; i < yAxes.length; i++) {
4545 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4548 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4551 if(event.altKey || event.shiftKey) {
4552 state.dygraph_user_action = true;
4554 state.globalSelectionSyncStop();
4555 state.globalSelectionSyncDelay();
4557 // http://dygraphs.com/gallery/interaction-api.js
4559 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4561 normal_def = event.wheelDelta / 40;
4564 normal_def = event.deltaY * -1.2;
4566 var normal = (event.detail) ? event.detail * -1 : normal_def;
4567 var percentage = normal / 50;
4569 if (!(event.offsetX && event.offsetY)){
4570 event.offsetX = event.layerX - event.target.offsetLeft;
4571 event.offsetY = event.layerY - event.target.offsetTop;
4574 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4575 var xPct = percentages[0];
4576 var yPct = percentages[1];
4578 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4579 var after = new_x_range[0];
4580 var before = new_x_range[1];
4582 var first = state.netdata_first + state.data_update_every;
4583 var last = state.netdata_last + state.data_update_every;
4586 after -= (before - last);
4593 state.setMode('zoom');
4594 if(state.updateChartPanOrZoom(after, before) === true)
4595 dygraph.updateOptions({ dateWindow: [ after, before ] });
4597 event.preventDefault();
4600 touchstart: function(event, dygraph, context) {
4601 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4602 state.log('interactionModel.touchstart()');
4604 state.dygraph_user_action = true;
4605 state.setMode('zoom');
4608 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4610 // we overwrite the touch directions at the end, to overwrite
4611 // the internal default of dygraphs
4612 context.touchDirections = { x: true, y: false };
4614 state.dygraph_last_touch_start = Date.now();
4615 state.dygraph_last_touch_move = 0;
4617 if(typeof event.touches[0].pageX === 'number')
4618 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4620 state.dygraph_last_touch_page_x = 0;
4622 touchmove: function(event, dygraph, context) {
4623 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4624 state.log('interactionModel.touchmove()');
4626 state.dygraph_user_action = true;
4627 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4629 state.dygraph_last_touch_move = Date.now();
4631 touchend: function(event, dygraph, context) {
4632 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4633 state.log('interactionModel.touchend()');
4635 state.dygraph_user_action = true;
4636 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4638 // if it didn't move, it is a selection
4639 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4640 // internal api of dygraphs
4641 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4642 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4643 if(NETDATA.dygraphSetSelection(state, t) === true)
4644 state.globalSelectionSync(t);
4647 // if it was double tap within double click time, reset the charts
4648 var now = Date.now();
4649 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4650 if(state.dygraph_last_touch_move === 0) {
4651 var dt = now - state.dygraph_last_touch_end;
4652 if(dt <= NETDATA.options.current.double_click_speed)
4653 NETDATA.resetAllCharts(state);
4657 // remember the timestamp of the last touch end
4658 state.dygraph_last_touch_end = now;
4663 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4664 state.dygraph_options.drawGrid = false;
4665 state.dygraph_options.drawAxis = false;
4666 state.dygraph_options.title = undefined;
4667 state.dygraph_options.ylabel = undefined;
4668 state.dygraph_options.yLabelWidth = 0;
4669 state.dygraph_options.labelsDivWidth = 120;
4670 state.dygraph_options.labelsDivStyles.width = '120px';
4671 state.dygraph_options.labelsSeparateLines = true;
4672 state.dygraph_options.rightGap = 0;
4673 state.dygraph_options.yRangePad = 1;
4676 if(smooth === true) {
4677 state.dygraph_smooth_eligible = true;
4679 if(NETDATA.options.current.smooth_plot === true)
4680 state.dygraph_options.plotter = smoothPlotter;
4682 else state.dygraph_smooth_eligible = false;
4684 state.dygraph_instance = new Dygraph(state.element_chart,
4685 data.result.data, state.dygraph_options);
4687 state.dygraph_force_zoom = false;
4688 state.dygraph_user_action = false;
4689 state.dygraph_last_rendered = Date.now();
4691 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4692 state.__commonMin = self.data('common-min') || null;
4693 state.__commonMax = self.data('common-max') || null;
4696 state.log('incompatible version of dygraphs detected');
4697 state.__commonMin = null;
4698 state.__commonMax = null;
4704 // ----------------------------------------------------------------------------------------------------------------
4707 NETDATA.morrisInitialize = function(callback) {
4708 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4710 // morris requires raphael
4711 if(!NETDATA.chartLibraries.raphael.initialized) {
4712 if(NETDATA.chartLibraries.raphael.enabled) {
4713 NETDATA.raphaelInitialize(function() {
4714 NETDATA.morrisInitialize(callback);
4718 NETDATA.chartLibraries.morris.enabled = false;
4719 if(typeof callback === "function")
4724 NETDATA._loadCSS(NETDATA.morris_css);
4727 url: NETDATA.morris_js,
4730 xhrFields: { withCredentials: true } // required for the cookie
4733 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4736 NETDATA.chartLibraries.morris.enabled = false;
4737 NETDATA.error(100, NETDATA.morris_js);
4739 .always(function() {
4740 if(typeof callback === "function")
4746 NETDATA.chartLibraries.morris.enabled = false;
4747 if(typeof callback === "function")
4752 NETDATA.morrisChartUpdate = function(state, data) {
4753 state.morris_instance.setData(data.result.data);
4757 NETDATA.morrisChartCreate = function(state, data) {
4759 state.morris_options = {
4760 element: state.element_chart.id,
4761 data: data.result.data,
4763 ykeys: data.dimension_names,
4764 labels: data.dimension_names,
4770 continuousLine: false,
4771 behaveLikeLine: false
4774 if(state.chart.chart_type === 'line')
4775 state.morris_instance = new Morris.Line(state.morris_options);
4777 else if(state.chart.chart_type === 'area') {
4778 state.morris_options.behaveLikeLine = true;
4779 state.morris_instance = new Morris.Area(state.morris_options);
4782 state.morris_instance = new Morris.Area(state.morris_options);
4787 // ----------------------------------------------------------------------------------------------------------------
4790 NETDATA.raphaelInitialize = function(callback) {
4791 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4793 url: NETDATA.raphael_js,
4796 xhrFields: { withCredentials: true } // required for the cookie
4799 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4802 NETDATA.chartLibraries.raphael.enabled = false;
4803 NETDATA.error(100, NETDATA.raphael_js);
4805 .always(function() {
4806 if(typeof callback === "function")
4811 NETDATA.chartLibraries.raphael.enabled = false;
4812 if(typeof callback === "function")
4817 NETDATA.raphaelChartUpdate = function(state, data) {
4818 $(state.element_chart).raphael(data.result, {
4819 width: state.chartWidth(),
4820 height: state.chartHeight()
4826 NETDATA.raphaelChartCreate = function(state, data) {
4827 $(state.element_chart).raphael(data.result, {
4828 width: state.chartWidth(),
4829 height: state.chartHeight()
4835 // ----------------------------------------------------------------------------------------------------------------
4838 NETDATA.c3Initialize = function(callback) {
4839 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4842 if(!NETDATA.chartLibraries.d3.initialized) {
4843 if(NETDATA.chartLibraries.d3.enabled) {
4844 NETDATA.d3Initialize(function() {
4845 NETDATA.c3Initialize(callback);
4849 NETDATA.chartLibraries.c3.enabled = false;
4850 if(typeof callback === "function")
4855 NETDATA._loadCSS(NETDATA.c3_css);
4861 xhrFields: { withCredentials: true } // required for the cookie
4864 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4867 NETDATA.chartLibraries.c3.enabled = false;
4868 NETDATA.error(100, NETDATA.c3_js);
4870 .always(function() {
4871 if(typeof callback === "function")
4877 NETDATA.chartLibraries.c3.enabled = false;
4878 if(typeof callback === "function")
4883 NETDATA.c3ChartUpdate = function(state, data) {
4884 state.c3_instance.destroy();
4885 return NETDATA.c3ChartCreate(state, data);
4887 //state.c3_instance.load({
4888 // rows: data.result,
4895 NETDATA.c3ChartCreate = function(state, data) {
4897 state.element_chart.id = 'c3-' + state.uuid;
4898 // console.log('id = ' + state.element_chart.id);
4900 state.c3_instance = c3.generate({
4901 bindto: '#' + state.element_chart.id,
4903 width: state.chartWidth(),
4904 height: state.chartHeight()
4907 pattern: state.chartColors()
4912 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4918 format: function(x) {
4919 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4946 // console.log(state.c3_instance);
4951 // ----------------------------------------------------------------------------------------------------------------
4954 NETDATA.d3Initialize = function(callback) {
4955 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4960 xhrFields: { withCredentials: true } // required for the cookie
4963 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4966 NETDATA.chartLibraries.d3.enabled = false;
4967 NETDATA.error(100, NETDATA.d3_js);
4969 .always(function() {
4970 if(typeof callback === "function")
4975 NETDATA.chartLibraries.d3.enabled = false;
4976 if(typeof callback === "function")
4981 NETDATA.d3ChartUpdate = function(state, data) {
4985 NETDATA.d3ChartCreate = function(state, data) {
4989 // ----------------------------------------------------------------------------------------------------------------
4992 NETDATA.googleInitialize = function(callback) {
4993 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4995 url: NETDATA.google_js,
4998 xhrFields: { withCredentials: true } // required for the cookie
5001 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5002 google.load('visualization', '1.1', {
5003 'packages': ['corechart', 'controls'],
5004 'callback': callback
5008 NETDATA.chartLibraries.google.enabled = false;
5009 NETDATA.error(100, NETDATA.google_js);
5010 if(typeof callback === "function")
5015 NETDATA.chartLibraries.google.enabled = false;
5016 if(typeof callback === "function")
5021 NETDATA.googleChartUpdate = function(state, data) {
5022 var datatable = new google.visualization.DataTable(data.result);
5023 state.google_instance.draw(datatable, state.google_options);
5027 NETDATA.googleChartCreate = function(state, data) {
5028 var datatable = new google.visualization.DataTable(data.result);
5030 state.google_options = {
5031 colors: state.chartColors(),
5033 // do not set width, height - the chart resizes itself
5034 //width: state.chartWidth(),
5035 //height: state.chartHeight(),
5040 // title: "Time of Day",
5041 // format:'HH:mm:ss',
5042 viewWindowMode: 'maximized',
5054 viewWindowMode: 'pretty',
5069 focusTarget: 'category',
5076 titlePosition: 'out',
5087 curveType: 'function',
5092 switch(state.chart.chart_type) {
5094 state.google_options.vAxis.viewWindowMode = 'maximized';
5095 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5096 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5100 state.google_options.isStacked = true;
5101 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5102 state.google_options.vAxis.viewWindowMode = 'maximized';
5103 state.google_options.vAxis.minValue = null;
5104 state.google_options.vAxis.maxValue = null;
5105 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5110 state.google_options.lineWidth = 2;
5111 state.google_instance = new google.visualization.LineChart(state.element_chart);
5115 state.google_instance.draw(datatable, state.google_options);
5119 // ----------------------------------------------------------------------------------------------------------------
5121 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5122 if(typeof value !== 'number') value = 0;
5123 if(typeof min !== 'number') min = 0;
5124 if(typeof max !== 'number') max = 0;
5126 if(min > value) min = value;
5127 if(max < value) max = value;
5129 // make sure it is zero based
5130 if(min > 0) min = 0;
5131 if(max < 0) max = 0;
5136 pcent = Math.round(value * 100 / max);
5137 if(pcent === 0) pcent = 0.1;
5141 pcent = Math.round(-value * 100 / min);
5142 if(pcent === 0) pcent = -0.1;
5148 // ----------------------------------------------------------------------------------------------------------------
5151 NETDATA.easypiechartInitialize = function(callback) {
5152 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5154 url: NETDATA.easypiechart_js,
5157 xhrFields: { withCredentials: true } // required for the cookie
5160 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5163 NETDATA.chartLibraries.easypiechart.enabled = false;
5164 NETDATA.error(100, NETDATA.easypiechart_js);
5166 .always(function() {
5167 if(typeof callback === "function")
5172 NETDATA.chartLibraries.easypiechart.enabled = false;
5173 if(typeof callback === "function")
5178 NETDATA.easypiechartClearSelection = function(state) {
5179 if(typeof state.easyPieChartEvent !== 'undefined') {
5180 if(state.easyPieChartEvent.timer !== null)
5181 clearTimeout(state.easyPieChartEvent.timer);
5183 state.easyPieChartEvent.timer = null;
5186 if(state.isAutoRefreshable() === true && state.data !== null) {
5187 NETDATA.easypiechartChartUpdate(state, state.data);
5190 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5191 state.easyPieChart_instance.update(0);
5193 state.easyPieChart_instance.enableAnimation();
5198 NETDATA.easypiechartSetSelection = function(state, t) {
5199 if(state.timeIsVisible(t) !== true)
5200 return NETDATA.easypiechartClearSelection(state);
5202 var slot = state.calculateRowForTime(t);
5203 if(slot < 0 || slot >= state.data.result.length)
5204 return NETDATA.easypiechartClearSelection(state);
5206 if(typeof state.easyPieChartEvent === 'undefined') {
5207 state.easyPieChartEvent = {
5214 var value = state.data.result[state.data.result.length - 1 - slot];
5215 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5216 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5217 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5219 state.easyPieChartEvent.value = value;
5220 state.easyPieChartEvent.pcent = pcent;
5221 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5223 if(state.easyPieChartEvent.timer === null) {
5224 state.easyPieChart_instance.disableAnimation();
5226 state.easyPieChartEvent.timer = setTimeout(function() {
5227 state.easyPieChartEvent.timer = null;
5228 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5229 }, NETDATA.options.current.charts_selection_animation_delay);
5235 NETDATA.easypiechartChartUpdate = function(state, data) {
5236 var value, min, max, pcent;
5238 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5243 value = data.result[0];
5244 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5245 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5246 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5249 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5250 state.easyPieChart_instance.update(pcent);
5254 NETDATA.easypiechartChartCreate = function(state, data) {
5255 var self = $(state.element);
5256 var chart = $(state.element_chart);
5258 var value = data.result[0];
5259 var min = self.data('easypiechart-min-value') || null;
5260 var max = self.data('easypiechart-max-value') || null;
5261 var adjust = self.data('easypiechart-adjust') || null;
5264 min = NETDATA.commonMin.get(state);
5265 state.easyPieChartMin = null;
5268 state.easyPieChartMin = min;
5271 max = NETDATA.commonMax.get(state);
5272 state.easyPieChartMax = null;
5275 state.easyPieChartMax = max;
5277 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5279 chart.data('data-percent', pcent);
5283 case 'width': size = state.chartHeight(); break;
5284 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5285 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5287 default: size = state.chartWidth(); break;
5289 state.element.style.width = size + 'px';
5290 state.element.style.height = size + 'px';
5292 var stroke = Math.floor(size / 22);
5293 if(stroke < 3) stroke = 2;
5295 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5296 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5297 state.easyPieChartLabel = document.createElement('span');
5298 state.easyPieChartLabel.className = 'easyPieChartLabel';
5299 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5300 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5301 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5302 state.element_chart.appendChild(state.easyPieChartLabel);
5304 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5305 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5306 state.easyPieChartTitle = document.createElement('span');
5307 state.easyPieChartTitle.className = 'easyPieChartTitle';
5308 state.easyPieChartTitle.innerText = state.title;
5309 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5310 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5311 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5312 state.element_chart.appendChild(state.easyPieChartTitle);
5314 var unitfontsize = Math.round(titlefontsize * 0.9);
5315 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5316 state.easyPieChartUnits = document.createElement('span');
5317 state.easyPieChartUnits.className = 'easyPieChartUnits';
5318 state.easyPieChartUnits.innerText = state.units;
5319 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5320 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5321 state.element_chart.appendChild(state.easyPieChartUnits);
5323 var barColor = self.data('easypiechart-barcolor');
5324 if(typeof barColor === 'undefined' || barColor === null)
5325 barColor = state.chartColors()[0];
5327 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5328 var tmp = eval(barColor);
5329 if(typeof tmp === 'function')
5333 chart.easyPieChart({
5335 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5336 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5337 scaleLength: self.data('easypiechart-scalelength') || 5,
5338 lineCap: self.data('easypiechart-linecap') || 'round',
5339 lineWidth: self.data('easypiechart-linewidth') || stroke,
5340 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5341 size: self.data('easypiechart-size') || size,
5342 rotate: self.data('easypiechart-rotate') || 0,
5343 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5344 easing: self.data('easypiechart-easing') || undefined
5347 // when we just re-create the chart
5348 // do not animate the first update
5350 if(typeof state.easyPieChart_instance !== 'undefined')
5353 state.easyPieChart_instance = chart.data('easyPieChart');
5354 if(animate === false) state.easyPieChart_instance.disableAnimation();
5355 state.easyPieChart_instance.update(pcent);
5356 if(animate === false) state.easyPieChart_instance.enableAnimation();
5360 // ----------------------------------------------------------------------------------------------------------------
5363 NETDATA.gaugeInitialize = function(callback) {
5364 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5366 url: NETDATA.gauge_js,
5369 xhrFields: { withCredentials: true } // required for the cookie
5372 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5375 NETDATA.chartLibraries.gauge.enabled = false;
5376 NETDATA.error(100, NETDATA.gauge_js);
5378 .always(function() {
5379 if(typeof callback === "function")
5384 NETDATA.chartLibraries.gauge.enabled = false;
5385 if(typeof callback === "function")
5390 NETDATA.gaugeAnimation = function(state, status) {
5393 if(typeof status === 'boolean' && status === false)
5395 else if(typeof status === 'number')
5398 // console.log('gauge speed ' + speed);
5399 state.gauge_instance.animationSpeed = speed;
5400 state.___gaugeOld__.speed = speed;
5403 NETDATA.gaugeSet = function(state, value, min, max) {
5404 if(typeof value !== 'number') value = 0;
5405 if(typeof min !== 'number') min = 0;
5406 if(typeof max !== 'number') max = 0;
5407 if(value > max) max = value;
5408 if(value < min) min = value;
5414 else if(min === max)
5417 // gauge.js has an issue if the needle
5418 // is smaller than min or larger than max
5419 // when we set the new values
5420 // the needle will go crazy
5422 // to prevent it, we always feed it
5423 // with a percentage, so that the needle
5424 // is always between min and max
5425 var pcent = (value - min) * 100 / (max - min);
5427 // these should never happen
5428 if(pcent < 0) pcent = 0;
5429 if(pcent > 100) pcent = 100;
5431 state.gauge_instance.set(pcent);
5432 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5434 state.___gaugeOld__.value = value;
5435 state.___gaugeOld__.min = min;
5436 state.___gaugeOld__.max = max;
5439 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5440 if(state.___gaugeOld__.valueLabel !== value) {
5441 state.___gaugeOld__.valueLabel = value;
5442 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5444 if(state.___gaugeOld__.minLabel !== min) {
5445 state.___gaugeOld__.minLabel = min;
5446 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5448 if(state.___gaugeOld__.maxLabel !== max) {
5449 state.___gaugeOld__.maxLabel = max;
5450 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5454 NETDATA.gaugeClearSelection = function(state) {
5455 if(typeof state.gaugeEvent !== 'undefined') {
5456 if(state.gaugeEvent.timer !== null)
5457 clearTimeout(state.gaugeEvent.timer);
5459 state.gaugeEvent.timer = null;
5462 if(state.isAutoRefreshable() === true && state.data !== null) {
5463 NETDATA.gaugeChartUpdate(state, state.data);
5466 NETDATA.gaugeAnimation(state, false);
5467 NETDATA.gaugeSet(state, null, null, null);
5468 NETDATA.gaugeSetLabels(state, null, null, null);
5471 NETDATA.gaugeAnimation(state, true);
5475 NETDATA.gaugeSetSelection = function(state, t) {
5476 if(state.timeIsVisible(t) !== true)
5477 return NETDATA.gaugeClearSelection(state);
5479 var slot = state.calculateRowForTime(t);
5480 if(slot < 0 || slot >= state.data.result.length)
5481 return NETDATA.gaugeClearSelection(state);
5483 if(typeof state.gaugeEvent === 'undefined') {
5484 state.gaugeEvent = {
5492 var value = state.data.result[state.data.result.length - 1 - slot];
5493 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5494 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5496 // make sure it is zero based
5497 if(min > 0) min = 0;
5498 if(max < 0) max = 0;
5500 state.gaugeEvent.value = value;
5501 state.gaugeEvent.min = min;
5502 state.gaugeEvent.max = max;
5503 NETDATA.gaugeSetLabels(state, value, min, max);
5505 if(state.gaugeEvent.timer === null) {
5506 NETDATA.gaugeAnimation(state, false);
5508 state.gaugeEvent.timer = setTimeout(function() {
5509 state.gaugeEvent.timer = null;
5510 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5511 }, NETDATA.options.current.charts_selection_animation_delay);
5517 NETDATA.gaugeChartUpdate = function(state, data) {
5518 var value, min, max;
5520 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5524 NETDATA.gaugeSetLabels(state, null, null, null);
5527 value = data.result[0];
5528 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5529 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5530 if(value < min) min = value;
5531 if(value > max) max = value;
5533 // make sure it is zero based
5534 if(min > 0) min = 0;
5535 if(max < 0) max = 0;
5537 NETDATA.gaugeSetLabels(state, value, min, max);
5540 NETDATA.gaugeSet(state, value, min, max);
5544 NETDATA.gaugeChartCreate = function(state, data) {
5545 var self = $(state.element);
5546 // var chart = $(state.element_chart);
5548 var value = data.result[0];
5549 var min = self.data('gauge-min-value') || null;
5550 var max = self.data('gauge-max-value') || null;
5551 var adjust = self.data('gauge-adjust') || null;
5552 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5553 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5554 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5555 var stopColor = self.data('gauge-stop-color') || void 0;
5556 var generateGradient = self.data('gauge-generate-gradient') || false;
5559 min = NETDATA.commonMin.get(state);
5560 state.gaugeMin = null;
5563 state.gaugeMin = min;
5566 max = NETDATA.commonMax.get(state);
5567 state.gaugeMax = null;
5570 state.gaugeMax = max;
5572 // make sure it is zero based
5573 if(min > 0) min = 0;
5574 if(max < 0) max = 0;
5576 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5578 // case 'width': width = height * ratio; break;
5580 // default: height = width / ratio; break;
5582 //state.element.style.width = width.toString() + 'px';
5583 //state.element.style.height = height.toString() + 'px';
5588 lines: 12, // The number of lines to draw
5589 angle: 0.15, // The length of each line
5590 lineWidth: 0.44, // 0.44 The line thickness
5592 length: 0.8, // 0.9 The radius of the inner circle
5593 strokeWidth: 0.035, // The rotation offset
5594 color: pointerColor // Fill color
5596 colorStart: startColor, // Colors
5597 colorStop: stopColor, // just experiment with them
5598 strokeColor: strokeColor, // to see which ones work best for you
5600 generateGradient: (generateGradient === true),
5604 if (generateGradient.constructor === Array) {
5606 // data-gauge-generate-gradient="[0, 50, 100]"
5607 // data-gauge-gradient-percent-color-0="#FFFFFF"
5608 // data-gauge-gradient-percent-color-50="#999900"
5609 // data-gauge-gradient-percent-color-100="#000000"
5611 options.percentColors = [];
5612 var len = generateGradient.length;
5614 var pcent = generateGradient[len];
5615 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5616 if(color !== false) {
5620 options.percentColors.unshift(a);
5623 if(options.percentColors.length === 0)
5624 delete options.percentColors;
5626 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5627 options.percentColors = [
5628 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5629 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5630 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5631 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5632 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5633 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5634 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5635 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5636 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5637 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5638 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5641 state.gauge_canvas = document.createElement('canvas');
5642 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5643 state.gauge_canvas.className = 'gaugeChart';
5644 state.gauge_canvas.width = width;
5645 state.gauge_canvas.height = height;
5646 state.element_chart.appendChild(state.gauge_canvas);
5648 var valuefontsize = Math.floor(height / 6);
5649 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5650 state.gaugeChartLabel = document.createElement('span');
5651 state.gaugeChartLabel.className = 'gaugeChartLabel';
5652 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5653 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5654 state.element_chart.appendChild(state.gaugeChartLabel);
5656 var titlefontsize = Math.round(valuefontsize / 2);
5658 state.gaugeChartTitle = document.createElement('span');
5659 state.gaugeChartTitle.className = 'gaugeChartTitle';
5660 state.gaugeChartTitle.innerText = state.title;
5661 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5662 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5663 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5664 state.element_chart.appendChild(state.gaugeChartTitle);
5666 var unitfontsize = Math.round(titlefontsize * 0.9);
5667 state.gaugeChartUnits = document.createElement('span');
5668 state.gaugeChartUnits.className = 'gaugeChartUnits';
5669 state.gaugeChartUnits.innerText = state.units;
5670 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5671 state.element_chart.appendChild(state.gaugeChartUnits);
5673 state.gaugeChartMin = document.createElement('span');
5674 state.gaugeChartMin.className = 'gaugeChartMin';
5675 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5676 state.element_chart.appendChild(state.gaugeChartMin);
5678 state.gaugeChartMax = document.createElement('span');
5679 state.gaugeChartMax.className = 'gaugeChartMax';
5680 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5681 state.element_chart.appendChild(state.gaugeChartMax);
5683 // when we just re-create the chart
5684 // do not animate the first update
5686 if(typeof state.gauge_instance !== 'undefined')
5689 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5691 state.___gaugeOld__ = {
5700 // we will always feed a percentage
5701 state.gauge_instance.minValue = 0;
5702 state.gauge_instance.maxValue = 100;
5704 NETDATA.gaugeAnimation(state, animate);
5705 NETDATA.gaugeSet(state, value, min, max);
5706 NETDATA.gaugeSetLabels(state, value, min, max);
5707 NETDATA.gaugeAnimation(state, true);
5711 // ----------------------------------------------------------------------------------------------------------------
5712 // Charts Libraries Registration
5714 NETDATA.chartLibraries = {
5716 initialize: NETDATA.dygraphInitialize,
5717 create: NETDATA.dygraphChartCreate,
5718 update: NETDATA.dygraphChartUpdate,
5719 resize: function(state) {
5720 if(typeof state.dygraph_instance.resize === 'function')
5721 state.dygraph_instance.resize();
5723 setSelection: NETDATA.dygraphSetSelection,
5724 clearSelection: NETDATA.dygraphClearSelection,
5725 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5728 format: function(state) { void(state); return 'json'; },
5729 options: function(state) { void(state); return 'ms|flip'; },
5730 legend: function(state) {
5731 return (this.isSparkline(state) === false)?'right-side':null;
5733 autoresize: function(state) { void(state); return true; },
5734 max_updates_to_recreate: function(state) { void(state); return 5000; },
5735 track_colors: function(state) { void(state); return true; },
5736 pixels_per_point: function(state) {
5737 return (this.isSparkline(state) === false)?3:2;
5739 isSparkline: function(state) {
5740 if(typeof state.dygraph_sparkline === 'undefined') {
5741 var t = $(state.element).data('dygraph-theme');
5742 state.dygraph_sparkline = (t === 'sparkline');
5744 return state.dygraph_sparkline;
5748 initialize: NETDATA.sparklineInitialize,
5749 create: NETDATA.sparklineChartCreate,
5750 update: NETDATA.sparklineChartUpdate,
5752 setSelection: undefined, // function(state, t) { void(state); return true; },
5753 clearSelection: undefined, // function(state) { void(state); return true; },
5754 toolboxPanAndZoom: null,
5757 format: function(state) { void(state); return 'array'; },
5758 options: function(state) { void(state); return 'flip|abs'; },
5759 legend: function(state) { void(state); return null; },
5760 autoresize: function(state) { void(state); return false; },
5761 max_updates_to_recreate: function(state) { void(state); return 5000; },
5762 track_colors: function(state) { void(state); return false; },
5763 pixels_per_point: function(state) { void(state); return 3; }
5766 initialize: NETDATA.peityInitialize,
5767 create: NETDATA.peityChartCreate,
5768 update: NETDATA.peityChartUpdate,
5770 setSelection: undefined, // function(state, t) { void(state); return true; },
5771 clearSelection: undefined, // function(state) { void(state); return true; },
5772 toolboxPanAndZoom: null,
5775 format: function(state) { void(state); return 'ssvcomma'; },
5776 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5777 legend: function(state) { void(state); return null; },
5778 autoresize: function(state) { void(state); return false; },
5779 max_updates_to_recreate: function(state) { void(state); return 5000; },
5780 track_colors: function(state) { void(state); return false; },
5781 pixels_per_point: function(state) { void(state); return 3; }
5784 initialize: NETDATA.morrisInitialize,
5785 create: NETDATA.morrisChartCreate,
5786 update: NETDATA.morrisChartUpdate,
5788 setSelection: undefined, // function(state, t) { void(state); return true; },
5789 clearSelection: undefined, // function(state) { void(state); return true; },
5790 toolboxPanAndZoom: null,
5793 format: function(state) { void(state); return 'json'; },
5794 options: function(state) { void(state); return 'objectrows|ms'; },
5795 legend: function(state) { void(state); return null; },
5796 autoresize: function(state) { void(state); return false; },
5797 max_updates_to_recreate: function(state) { void(state); return 50; },
5798 track_colors: function(state) { void(state); return false; },
5799 pixels_per_point: function(state) { void(state); return 15; }
5802 initialize: NETDATA.googleInitialize,
5803 create: NETDATA.googleChartCreate,
5804 update: NETDATA.googleChartUpdate,
5806 setSelection: undefined, //function(state, t) { void(state); return true; },
5807 clearSelection: undefined, //function(state) { void(state); return true; },
5808 toolboxPanAndZoom: null,
5811 format: function(state) { void(state); return 'datatable'; },
5812 options: function(state) { void(state); return ''; },
5813 legend: function(state) { void(state); return null; },
5814 autoresize: function(state) { void(state); return false; },
5815 max_updates_to_recreate: function(state) { void(state); return 300; },
5816 track_colors: function(state) { void(state); return false; },
5817 pixels_per_point: function(state) { void(state); return 4; }
5820 initialize: NETDATA.raphaelInitialize,
5821 create: NETDATA.raphaelChartCreate,
5822 update: NETDATA.raphaelChartUpdate,
5824 setSelection: undefined, // function(state, t) { void(state); return true; },
5825 clearSelection: undefined, // function(state) { void(state); return true; },
5826 toolboxPanAndZoom: null,
5829 format: function(state) { void(state); return 'json'; },
5830 options: function(state) { void(state); return ''; },
5831 legend: function(state) { void(state); return null; },
5832 autoresize: function(state) { void(state); return false; },
5833 max_updates_to_recreate: function(state) { void(state); return 5000; },
5834 track_colors: function(state) { void(state); return false; },
5835 pixels_per_point: function(state) { void(state); return 3; }
5838 initialize: NETDATA.c3Initialize,
5839 create: NETDATA.c3ChartCreate,
5840 update: NETDATA.c3ChartUpdate,
5842 setSelection: undefined, // function(state, t) { void(state); return true; },
5843 clearSelection: undefined, // function(state) { void(state); return true; },
5844 toolboxPanAndZoom: null,
5847 format: function(state) { void(state); return 'csvjsonarray'; },
5848 options: function(state) { void(state); return 'milliseconds'; },
5849 legend: function(state) { void(state); return null; },
5850 autoresize: function(state) { void(state); return false; },
5851 max_updates_to_recreate: function(state) { void(state); return 5000; },
5852 track_colors: function(state) { void(state); return false; },
5853 pixels_per_point: function(state) { void(state); return 15; }
5856 initialize: NETDATA.d3Initialize,
5857 create: NETDATA.d3ChartCreate,
5858 update: NETDATA.d3ChartUpdate,
5860 setSelection: undefined, // function(state, t) { void(state); return true; },
5861 clearSelection: undefined, // function(state) { void(state); return true; },
5862 toolboxPanAndZoom: null,
5865 format: function(state) { void(state); return 'json'; },
5866 options: function(state) { void(state); return ''; },
5867 legend: function(state) { void(state); return null; },
5868 autoresize: function(state) { void(state); return false; },
5869 max_updates_to_recreate: function(state) { void(state); return 5000; },
5870 track_colors: function(state) { void(state); return false; },
5871 pixels_per_point: function(state) { void(state); return 3; }
5874 initialize: NETDATA.easypiechartInitialize,
5875 create: NETDATA.easypiechartChartCreate,
5876 update: NETDATA.easypiechartChartUpdate,
5878 setSelection: NETDATA.easypiechartSetSelection,
5879 clearSelection: NETDATA.easypiechartClearSelection,
5880 toolboxPanAndZoom: null,
5883 format: function(state) { void(state); return 'array'; },
5884 options: function(state) { void(state); return 'absolute'; },
5885 legend: function(state) { void(state); return null; },
5886 autoresize: function(state) { void(state); return false; },
5887 max_updates_to_recreate: function(state) { void(state); return 5000; },
5888 track_colors: function(state) { void(state); return true; },
5889 pixels_per_point: function(state) { void(state); return 3; },
5893 initialize: NETDATA.gaugeInitialize,
5894 create: NETDATA.gaugeChartCreate,
5895 update: NETDATA.gaugeChartUpdate,
5897 setSelection: NETDATA.gaugeSetSelection,
5898 clearSelection: NETDATA.gaugeClearSelection,
5899 toolboxPanAndZoom: null,
5902 format: function(state) { void(state); return 'array'; },
5903 options: function(state) { void(state); return 'absolute'; },
5904 legend: function(state) { void(state); return null; },
5905 autoresize: function(state) { void(state); return false; },
5906 max_updates_to_recreate: function(state) { void(state); return 5000; },
5907 track_colors: function(state) { void(state); return true; },
5908 pixels_per_point: function(state) { void(state); return 3; },
5913 NETDATA.registerChartLibrary = function(library, url) {
5914 if(NETDATA.options.debug.libraries === true)
5915 console.log("registering chart library: " + library);
5917 NETDATA.chartLibraries[library].url = url;
5918 NETDATA.chartLibraries[library].initialized = true;
5919 NETDATA.chartLibraries[library].enabled = true;
5922 // ----------------------------------------------------------------------------------------------------------------
5923 // Load required JS libraries and CSS
5925 NETDATA.requiredJs = [
5927 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5929 isAlreadyLoaded: function() {
5930 // check if bootstrap is loaded
5931 if(typeof $().emulateTransitionEnd === 'function')
5934 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5939 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5940 isAlreadyLoaded: function() { return false; }
5944 NETDATA.requiredCSS = [
5946 url: NETDATA.themes.current.bootstrap_css,
5947 isAlreadyLoaded: function() {
5948 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5952 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5953 isAlreadyLoaded: function() { return false; }
5956 url: NETDATA.themes.current.dashboard_css,
5957 isAlreadyLoaded: function() { return false; }
5961 NETDATA.loadedRequiredJs = 0;
5962 NETDATA.loadRequiredJs = function(index, callback) {
5963 if(index >= NETDATA.requiredJs.length) {
5964 if(typeof callback === 'function')
5969 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5970 NETDATA.loadedRequiredJs++;
5971 NETDATA.loadRequiredJs(++index, callback);
5975 if(NETDATA.options.debug.main_loop === true)
5976 console.log('loading ' + NETDATA.requiredJs[index].url);
5979 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5983 url: NETDATA.requiredJs[index].url,
5986 xhrFields: { withCredentials: true } // required for the cookie
5989 if(NETDATA.options.debug.main_loop === true)
5990 console.log('loaded ' + NETDATA.requiredJs[index].url);
5993 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5995 .always(function() {
5996 NETDATA.loadedRequiredJs++;
5999 NETDATA.loadRequiredJs(++index, callback);
6003 NETDATA.loadRequiredJs(++index, callback);
6006 NETDATA.loadRequiredCSS = function(index) {
6007 if(index >= NETDATA.requiredCSS.length)
6010 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6011 NETDATA.loadRequiredCSS(++index);
6015 if(NETDATA.options.debug.main_loop === true)
6016 console.log('loading ' + NETDATA.requiredCSS[index].url);
6018 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6019 NETDATA.loadRequiredCSS(++index);
6023 // ----------------------------------------------------------------------------------------------------------------
6024 // Registry of netdata hosts
6027 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6028 chart_div_offset: 100, // give that space above the chart when scrolling to it
6029 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6030 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6032 ms_penalty: 0, // the time penalty of the next alarm
6033 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6034 // if alarms are shown faster than: one per 500ms
6036 notifications: false, // when true, the browser supports notifications (may not be granted though)
6037 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6038 first_notification_id: 0, // the id of the first alarm_log entry for this session
6039 // this is used to prevent CLEAR notifications for past events
6040 // notifications_shown: [],
6042 server: null, // the server to connect to for fetching alarms
6043 current: null, // the list of raised alarms - updated in the background
6044 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6046 notify: function(entry) {
6047 // console.log('alarm ' + entry.unique_id);
6049 if(entry.updated === true) {
6050 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6054 var value_string = entry.value_string;
6056 if(NETDATA.alarms.current !== null) {
6057 // get the current value_string
6058 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6059 if(typeof t !== 'undefined' && entry.status === t.status)
6060 value_string = t.value_string;
6063 var name = entry.name.replace(/_/g, ' ');
6064 var status = entry.status.toLowerCase();
6065 var title = name + ' = ' + value_string.toString();
6066 var tag = entry.alarm_id;
6067 var icon = 'images/seo-performance-128.png';
6068 var interaction = false;
6072 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6074 switch(entry.status) {
6082 case 'UNINITIALIZED':
6086 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6087 // console.log('alarm ' + entry.unique_id + ' is not current');
6090 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6091 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6094 if(entry.no_clear_notification === true) {
6095 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6098 title = name + ' back to normal (' + value_string.toString() + ')';
6099 icon = 'images/check-mark-2-128-green.png'
6100 interaction = false;
6104 if(entry.old_status === 'CRITICAL')
6105 status = 'demoted to ' + entry.status.toLowerCase();
6107 icon = 'images/alert-128-orange.png';
6108 interaction = false;
6112 if(entry.old_status === 'WARNING')
6113 status = 'escalated to ' + entry.status.toLowerCase();
6115 icon = 'images/alert-128-red.png'
6120 console.log('invalid alarm status ' + entry.status);
6125 // cleanup old notifications with the same alarm_id as this one
6126 // FIXME: it does not seem to work on any web browser!
6127 var len = NETDATA.alarms.notifications_shown.length;
6129 var n = NETDATA.alarms.notifications_shown[len];
6130 if(n.data.alarm_id === entry.alarm_id) {
6131 console.log('removing old alarm ' + n.data.unique_id);
6133 // close the notification
6136 // remove it from the array
6137 NETDATA.alarms.notifications_shown.splice(len, 1);
6138 len = NETDATA.alarms.notifications_shown.length;
6145 setTimeout(function() {
6146 // show this notification
6147 // console.log('new notification: ' + title);
6148 var n = new Notification(title, {
6149 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6151 requireInteraction: interaction,
6152 icon: NETDATA.serverDefault + icon,
6156 n.onclick = function(event) {
6157 event.preventDefault();
6158 NETDATA.alarms.onclick(event.target.data);
6162 // NETDATA.alarms.notifications_shown.push(n);
6163 // console.log(entry);
6164 }, NETDATA.alarms.ms_penalty);
6166 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6170 scrollToChart: function(chart_id) {
6171 if(typeof chart_id === 'string') {
6172 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6173 if(typeof offset !== 'undefined') {
6174 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6181 scrollToAlarm: function(alarm) {
6182 if(typeof alarm === 'object') {
6183 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6185 if(ret === true && NETDATA.options.page_is_visible === false)
6187 // 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.');
6192 notifyAll: function() {
6193 // console.log('FETCHING ALARM LOG');
6194 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6195 // console.log('ALARM LOG FETCHED');
6197 if(data === null || typeof data !== 'object') {
6198 console.log('invalid alarms log response');
6202 if(data.length === 0) {
6203 console.log('received empty alarm log');
6207 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6209 data.sort(function(a, b) {
6210 if(a.unique_id > b.unique_id) return -1;
6211 if(a.unique_id < b.unique_id) return 1;
6215 NETDATA.alarms.ms_penalty = 0;
6217 var len = data.length;
6219 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6220 NETDATA.alarms.notify(data[len]);
6223 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6226 NETDATA.alarms.last_notification_id = data[0].unique_id;
6227 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6228 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6232 check_notifications: function() {
6233 // returns true if we should fire 1+ notifications
6235 if(NETDATA.alarms.notifications !== true) {
6236 // console.log('notifications not available');
6240 if(Notification.permission !== 'granted') {
6241 // console.log('notifications not granted');
6245 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6246 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6248 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6249 // console.log('new alarms detected');
6252 //else console.log('no new alarms');
6254 // else console.log('cannot process alarms');
6259 get: function(what, callback) {
6261 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6265 'Cache-Control': 'no-cache, no-store',
6266 'Pragma': 'no-cache'
6268 xhrFields: { withCredentials: true } // required for the cookie
6270 .done(function(data) {
6271 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6272 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6274 if(typeof callback === 'function')
6275 return callback(data);
6278 NETDATA.error(415, NETDATA.alarms.server);
6280 if(typeof callback === 'function')
6281 return callback(null);
6285 update_forever: function() {
6286 NETDATA.alarms.get('active', function(data) {
6288 NETDATA.alarms.current = data;
6290 if(NETDATA.alarms.check_notifications() === true) {
6291 NETDATA.alarms.notifyAll();
6294 if (typeof NETDATA.alarms.callback === 'function') {
6295 NETDATA.alarms.callback(data);
6298 // Health monitoring is disabled on this netdata
6299 if(data.status === false) return;
6302 setTimeout(NETDATA.alarms.update_forever, 10000);
6306 get_log: function(last_id, callback) {
6307 // console.log('fetching all log after ' + last_id.toString());
6309 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6313 'Cache-Control': 'no-cache, no-store',
6314 'Pragma': 'no-cache'
6316 xhrFields: { withCredentials: true } // required for the cookie
6318 .done(function(data) {
6319 if(typeof callback === 'function')
6320 return callback(data);
6323 NETDATA.error(416, NETDATA.alarms.server);
6325 if(typeof callback === 'function')
6326 return callback(null);
6331 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6333 NETDATA.alarms.last_notification_id =
6334 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6336 if(NETDATA.alarms.onclick === null)
6337 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6339 if(netdataShowAlarms === true) {
6340 NETDATA.alarms.update_forever();
6342 if('Notification' in window) {
6343 // console.log('notifications available');
6344 NETDATA.alarms.notifications = true;
6346 if(Notification.permission === 'default')
6347 Notification.requestPermission();
6353 // ----------------------------------------------------------------------------------------------------------------
6354 // Registry of netdata hosts
6356 NETDATA.registry = {
6357 server: null, // the netdata registry server
6358 person_guid: null, // the unique ID of this browser / user
6359 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6360 hostname: null, // the hostname of the netdata server that served dashboard.js
6361 machines: null, // the user's other URLs
6362 machines_array: null, // the user's other URLs in an array
6365 parsePersonUrls: function(person_urls) {
6366 // console.log(person_urls);
6367 NETDATA.registry.person_urls = person_urls;
6370 NETDATA.registry.machines = {};
6371 NETDATA.registry.machines_array = [];
6373 var now = Date.now();
6374 var apu = person_urls;
6377 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6378 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6384 accesses: apu[i][3],
6388 obj.alternate_urls.push(apu[i][1]);
6390 NETDATA.registry.machines[apu[i][0]] = obj;
6391 NETDATA.registry.machines_array.push(obj);
6394 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6396 var pu = NETDATA.registry.machines[apu[i][0]];
6397 if(pu.last_t < apu[i][2]) {
6399 pu.last_t = apu[i][2];
6400 pu.name = apu[i][4];
6402 pu.accesses += apu[i][3];
6403 pu.alternate_urls.push(apu[i][1]);
6408 if(typeof netdataRegistryCallback === 'function')
6409 netdataRegistryCallback(NETDATA.registry.machines_array);
6413 if(netdataRegistry !== true) return;
6415 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6417 NETDATA.registry.server = data.registry;
6418 NETDATA.registry.machine_guid = data.machine_guid;
6419 NETDATA.registry.hostname = data.hostname;
6421 NETDATA.registry.access(2, function (person_urls) {
6422 NETDATA.registry.parsePersonUrls(person_urls);
6429 hello: function(host, callback) {
6430 host = NETDATA.fixHost(host);
6432 // send HELLO to a netdata server:
6433 // 1. verifies the server is reachable
6434 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6436 url: host + '/api/v1/registry?action=hello',
6440 'Cache-Control': 'no-cache, no-store',
6441 'Pragma': 'no-cache'
6443 xhrFields: { withCredentials: true } // required for the cookie
6445 .done(function(data) {
6446 if(typeof data.status !== 'string' || data.status !== 'ok') {
6447 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6451 if(typeof callback === 'function')
6452 return callback(data);
6455 NETDATA.error(407, host);
6457 if(typeof callback === 'function')
6458 return callback(null);
6462 access: function(max_redirects, callback) {
6463 // send ACCESS to a netdata registry:
6464 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6465 // 2. it responds with a list of netdata servers we know
6466 // the registry identifies us using a cookie it sets the first time we access it
6467 // the registry may respond with a redirect URL to send us to another registry
6469 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),
6473 'Cache-Control': 'no-cache, no-store',
6474 'Pragma': 'no-cache'
6476 xhrFields: { withCredentials: true } // required for the cookie
6478 .done(function(data) {
6479 var redirect = null;
6480 if(typeof data.registry === 'string')
6481 redirect = data.registry;
6483 if(typeof data.status !== 'string' || data.status !== 'ok') {
6484 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6489 if(redirect !== null && max_redirects > 0) {
6490 NETDATA.registry.server = redirect;
6491 NETDATA.registry.access(max_redirects - 1, callback);
6494 if(typeof callback === 'function')
6495 return callback(null);
6499 if(typeof data.person_guid === 'string')
6500 NETDATA.registry.person_guid = data.person_guid;
6502 if(typeof callback === 'function')
6503 return callback(data.urls);
6507 NETDATA.error(410, NETDATA.registry.server);
6509 if(typeof callback === 'function')
6510 return callback(null);
6514 delete: function(delete_url, callback) {
6515 // send DELETE to a netdata registry:
6517 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),
6521 'Cache-Control': 'no-cache, no-store',
6522 'Pragma': 'no-cache'
6524 xhrFields: { withCredentials: true } // required for the cookie
6526 .done(function(data) {
6527 if(typeof data.status !== 'string' || data.status !== 'ok') {
6528 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6532 if(typeof callback === 'function')
6533 return callback(data);
6536 NETDATA.error(412, NETDATA.registry.server);
6538 if(typeof callback === 'function')
6539 return callback(null);
6543 search: function(machine_guid, callback) {
6544 // SEARCH for the URLs of a machine:
6546 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,
6550 'Cache-Control': 'no-cache, no-store',
6551 'Pragma': 'no-cache'
6553 xhrFields: { withCredentials: true } // required for the cookie
6555 .done(function(data) {
6556 if(typeof data.status !== 'string' || data.status !== 'ok') {
6557 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6561 if(typeof callback === 'function')
6562 return callback(data);
6565 NETDATA.error(418, NETDATA.registry.server);
6567 if(typeof callback === 'function')
6568 return callback(null);
6572 switch: function(new_person_guid, callback) {
6575 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,
6579 'Cache-Control': 'no-cache, no-store',
6580 'Pragma': 'no-cache'
6582 xhrFields: { withCredentials: true } // required for the cookie
6584 .done(function(data) {
6585 if(typeof data.status !== 'string' || data.status !== 'ok') {
6586 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6590 if(typeof callback === 'function')
6591 return callback(data);
6594 NETDATA.error(414, NETDATA.registry.server);
6596 if(typeof callback === 'function')
6597 return callback(null);
6602 // ----------------------------------------------------------------------------------------------------------------
6605 if(typeof netdataPrepCallback === 'function')
6606 netdataPrepCallback();
6608 NETDATA.errorReset();
6609 NETDATA.loadRequiredCSS(0);
6611 NETDATA._loadjQuery(function() {
6612 NETDATA.loadRequiredJs(0, function() {
6613 if(typeof $().emulateTransitionEnd !== 'function') {
6614 // bootstrap is not available
6615 NETDATA.options.current.show_help = false;
6618 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6619 if(NETDATA.options.debug.main_loop === true)
6620 console.log('starting chart refresh thread');
6626 })(window, document);