1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
4 /*global netdataNoDygraphs *//* boolean, disable dygraph charts
6 /*global netdataNoSparklines *//* boolean, disable sparkline charts
8 /*global netdataNoPeitys *//* boolean, disable peity charts
10 /*global netdataNoGoogleCharts *//* boolean, disable google charts
12 /*global netdataNoMorris *//* boolean, disable morris charts
14 /*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
16 /*global netdataNoGauge *//* boolean, disable gauge.js charts
18 /*global netdataNoD3 *//* boolean, disable d3 charts
20 /*global netdataNoC3 *//* boolean, disable c3 charts
22 /*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
24 /*global netdataDontStart *//* boolean, do not start the thread to process the charts
26 /*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
28 /*global netdataRegistry:true *//* boolean, use the netdata registry
30 /*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
31 * (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
34 /*global netdataShowHelp:true *//* boolean, disable charts help
36 /*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
40 /*global netdataCallback *//* function, callback to be called when netdata is ready to start
42 * netdata will be running while this is called
43 * (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
46 /*global netdataServer *//* string, the URL of the netdata server to use
47 * (default: the URL the page is hosted at) */
49 // ----------------------------------------------------------------------------
52 var NETDATA = window.NETDATA || {};
54 (function(window, document) {
55 // ------------------------------------------------------------------------
56 // compatibility fixes
58 // fix IE issue with console
59 if(!window.console) { window.console = { log: function(){} }; }
61 // if string.endsWith is not defined, define it
62 if(typeof String.prototype.endsWith !== 'function') {
63 String.prototype.endsWith = function(s) {
64 if(s.length > this.length) return false;
65 return this.slice(-s.length) === s;
69 // if string.startsWith is not defined, define it
70 if(typeof String.prototype.startsWith !== 'function') {
71 String.prototype.startsWith = function(s) {
72 if(s.length > this.length) return false;
73 return this.slice(s.length) === s;
77 NETDATA.name2id = function(s) {
86 // ----------------------------------------------------------------------------------------------------------------
87 // Detect the netdata server
89 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91 NETDATA._scriptSource = function() {
94 if(typeof document.currentScript !== 'undefined') {
95 script = document.currentScript;
98 var all_scripts = document.getElementsByTagName('script');
99 script = all_scripts[all_scripts.length - 1];
102 if (typeof script.getAttribute.length !== 'undefined')
105 script = script.getAttribute('src', -1);
110 if(typeof netdataServer !== 'undefined')
111 NETDATA.serverDefault = netdataServer;
113 var s = NETDATA._scriptSource();
114 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
116 console.log('WARNING: Cannot detect the URL of the netdata server.');
117 NETDATA.serverDefault = null;
121 if(NETDATA.serverDefault === null)
122 NETDATA.serverDefault = '';
123 else if(NETDATA.serverDefault.slice(-1) !== '/')
124 NETDATA.serverDefault += '/';
126 // default URLs for all the external files we need
127 // make them RELATIVE so that the whole thing can also be
128 // installed under a web server
129 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-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 solid 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 continuously.
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 internally, 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 continuously until this time of continuous
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 zoom a chart, the background
299 // auto-refreshing 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 synchronized
918 // to this time-range
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.enabled = true; // boolean - is the chart enabled for refresh?
1328 this.paused = false; // boolean - is the chart paused for any reason?
1329 this.selected = false; // boolean - is the chart shown a selection?
1330 this.debug = false; // boolean - console.log() debug info about this chart
1332 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1333 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1334 this.requested_after = null; // milliseconds - the timestamp of the request after param
1335 this.requested_before = null; // milliseconds - the timestamp of the request before param
1336 this.requested_padding = null;
1337 this.view_after = 0;
1338 this.view_before = 0;
1340 this.value_decimal_detail = -1;
1341 var d = self.data('decimal-digits');
1342 if(typeof d === 'number') {
1343 this.value_decimal_detail = 1;
1345 this.value_decimal_detail *= 10;
1351 force_update_at: 0, // the timestamp to force the update at
1352 force_before_ms: null,
1353 force_after_ms: null
1358 force_update_at: 0, // the timestamp to force the update at
1359 force_before_ms: null,
1360 force_after_ms: null
1365 force_update_at: 0, // the timestamp to force the update at
1366 force_before_ms: null,
1367 force_after_ms: null
1370 // this is a pointer to one of the sub-classes below
1372 this.current = this.auto;
1374 // check the requested library is available
1375 // we don't initialize it here - it will be initialized when
1376 // this chart will be first used
1377 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1378 NETDATA.error(402, that.library_name);
1379 error('chart library "' + that.library_name + '" is not found');
1382 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1383 NETDATA.error(403, that.library_name);
1384 error('chart library "' + that.library_name + '" is not enabled');
1388 that.library = NETDATA.chartLibraries[that.library_name];
1390 // milliseconds - the time the last refresh took
1391 this.refresh_dt_ms = 0;
1393 // if we need to report the rendering speed
1394 // find the element that needs to be updated
1395 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1397 if(refresh_dt_element_name !== null) {
1398 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 dimensions. 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('adjusting 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('adjusting 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 highest 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 dygraph
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 dygraph
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 Dygraph 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) {
4988 NETDATA.d3ChartCreate = function(state, data) {
4995 // ----------------------------------------------------------------------------------------------------------------
4998 NETDATA.googleInitialize = function(callback) {
4999 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5001 url: NETDATA.google_js,
5004 xhrFields: { withCredentials: true } // required for the cookie
5007 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5008 google.load('visualization', '1.1', {
5009 'packages': ['corechart', 'controls'],
5010 'callback': callback
5014 NETDATA.chartLibraries.google.enabled = false;
5015 NETDATA.error(100, NETDATA.google_js);
5016 if(typeof callback === "function")
5021 NETDATA.chartLibraries.google.enabled = false;
5022 if(typeof callback === "function")
5027 NETDATA.googleChartUpdate = function(state, data) {
5028 var datatable = new google.visualization.DataTable(data.result);
5029 state.google_instance.draw(datatable, state.google_options);
5033 NETDATA.googleChartCreate = function(state, data) {
5034 var datatable = new google.visualization.DataTable(data.result);
5036 state.google_options = {
5037 colors: state.chartColors(),
5039 // do not set width, height - the chart resizes itself
5040 //width: state.chartWidth(),
5041 //height: state.chartHeight(),
5046 // title: "Time of Day",
5047 // format:'HH:mm:ss',
5048 viewWindowMode: 'maximized',
5060 viewWindowMode: 'pretty',
5075 focusTarget: 'category',
5082 titlePosition: 'out',
5093 curveType: 'function',
5098 switch(state.chart.chart_type) {
5100 state.google_options.vAxis.viewWindowMode = 'maximized';
5101 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5102 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5106 state.google_options.isStacked = true;
5107 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5108 state.google_options.vAxis.viewWindowMode = 'maximized';
5109 state.google_options.vAxis.minValue = null;
5110 state.google_options.vAxis.maxValue = null;
5111 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5116 state.google_options.lineWidth = 2;
5117 state.google_instance = new google.visualization.LineChart(state.element_chart);
5121 state.google_instance.draw(datatable, state.google_options);
5125 // ----------------------------------------------------------------------------------------------------------------
5127 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5128 if(typeof value !== 'number') value = 0;
5129 if(typeof min !== 'number') min = 0;
5130 if(typeof max !== 'number') max = 0;
5132 if(min > value) min = value;
5133 if(max < value) max = value;
5135 // make sure it is zero based
5136 if(min > 0) min = 0;
5137 if(max < 0) max = 0;
5142 pcent = Math.round(value * 100 / max);
5143 if(pcent === 0) pcent = 0.1;
5147 pcent = Math.round(-value * 100 / min);
5148 if(pcent === 0) pcent = -0.1;
5154 // ----------------------------------------------------------------------------------------------------------------
5157 NETDATA.easypiechartInitialize = function(callback) {
5158 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5160 url: NETDATA.easypiechart_js,
5163 xhrFields: { withCredentials: true } // required for the cookie
5166 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5169 NETDATA.chartLibraries.easypiechart.enabled = false;
5170 NETDATA.error(100, NETDATA.easypiechart_js);
5172 .always(function() {
5173 if(typeof callback === "function")
5178 NETDATA.chartLibraries.easypiechart.enabled = false;
5179 if(typeof callback === "function")
5184 NETDATA.easypiechartClearSelection = function(state) {
5185 if(typeof state.easyPieChartEvent !== 'undefined') {
5186 if(state.easyPieChartEvent.timer !== undefined) {
5187 clearTimeout(state.easyPieChartEvent.timer);
5190 state.easyPieChartEvent.timer = undefined;
5193 if(state.isAutoRefreshable() === true && state.data !== null) {
5194 NETDATA.easypiechartChartUpdate(state, state.data);
5197 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5198 state.easyPieChart_instance.update(0);
5200 state.easyPieChart_instance.enableAnimation();
5205 NETDATA.easypiechartSetSelection = function(state, t) {
5206 if(state.timeIsVisible(t) !== true)
5207 return NETDATA.easypiechartClearSelection(state);
5209 var slot = state.calculateRowForTime(t);
5210 if(slot < 0 || slot >= state.data.result.length)
5211 return NETDATA.easypiechartClearSelection(state);
5213 if(typeof state.easyPieChartEvent === 'undefined') {
5214 state.easyPieChartEvent = {
5221 var value = state.data.result[state.data.result.length - 1 - slot];
5222 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5223 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5224 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5226 state.easyPieChartEvent.value = value;
5227 state.easyPieChartEvent.pcent = pcent;
5228 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5230 if(state.easyPieChartEvent.timer === undefined) {
5231 state.easyPieChart_instance.disableAnimation();
5233 state.easyPieChartEvent.timer = setTimeout(function() {
5234 state.easyPieChartEvent.timer = undefined;
5235 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5236 }, NETDATA.options.current.charts_selection_animation_delay);
5242 NETDATA.easypiechartChartUpdate = function(state, data) {
5243 var value, min, max, pcent;
5245 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5250 value = data.result[0];
5251 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5252 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5253 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5256 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5257 state.easyPieChart_instance.update(pcent);
5261 NETDATA.easypiechartChartCreate = function(state, data) {
5262 var self = $(state.element);
5263 var chart = $(state.element_chart);
5265 var value = data.result[0];
5266 var min = self.data('easypiechart-min-value') || null;
5267 var max = self.data('easypiechart-max-value') || null;
5268 var adjust = self.data('easypiechart-adjust') || null;
5271 min = NETDATA.commonMin.get(state);
5272 state.easyPieChartMin = null;
5275 state.easyPieChartMin = min;
5278 max = NETDATA.commonMax.get(state);
5279 state.easyPieChartMax = null;
5282 state.easyPieChartMax = max;
5284 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5286 chart.data('data-percent', pcent);
5290 case 'width': size = state.chartHeight(); break;
5291 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5292 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5294 default: size = state.chartWidth(); break;
5296 state.element.style.width = size + 'px';
5297 state.element.style.height = size + 'px';
5299 var stroke = Math.floor(size / 22);
5300 if(stroke < 3) stroke = 2;
5302 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5303 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5304 state.easyPieChartLabel = document.createElement('span');
5305 state.easyPieChartLabel.className = 'easyPieChartLabel';
5306 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5307 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5308 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5309 state.element_chart.appendChild(state.easyPieChartLabel);
5311 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5312 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5313 state.easyPieChartTitle = document.createElement('span');
5314 state.easyPieChartTitle.className = 'easyPieChartTitle';
5315 state.easyPieChartTitle.innerText = state.title;
5316 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5317 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5318 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5319 state.element_chart.appendChild(state.easyPieChartTitle);
5321 var unitfontsize = Math.round(titlefontsize * 0.9);
5322 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5323 state.easyPieChartUnits = document.createElement('span');
5324 state.easyPieChartUnits.className = 'easyPieChartUnits';
5325 state.easyPieChartUnits.innerText = state.units;
5326 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5327 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5328 state.element_chart.appendChild(state.easyPieChartUnits);
5330 var barColor = self.data('easypiechart-barcolor');
5331 if(typeof barColor === 'undefined' || barColor === null)
5332 barColor = state.chartColors()[0];
5334 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5335 var tmp = eval(barColor);
5336 if(typeof tmp === 'function')
5340 chart.easyPieChart({
5342 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5343 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5344 scaleLength: self.data('easypiechart-scalelength') || 5,
5345 lineCap: self.data('easypiechart-linecap') || 'round',
5346 lineWidth: self.data('easypiechart-linewidth') || stroke,
5347 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5348 size: self.data('easypiechart-size') || size,
5349 rotate: self.data('easypiechart-rotate') || 0,
5350 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5351 easing: self.data('easypiechart-easing') || undefined
5354 // when we just re-create the chart
5355 // do not animate the first update
5357 if(typeof state.easyPieChart_instance !== 'undefined')
5360 state.easyPieChart_instance = chart.data('easyPieChart');
5361 if(animate === false) state.easyPieChart_instance.disableAnimation();
5362 state.easyPieChart_instance.update(pcent);
5363 if(animate === false) state.easyPieChart_instance.enableAnimation();
5367 // ----------------------------------------------------------------------------------------------------------------
5370 NETDATA.gaugeInitialize = function(callback) {
5371 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5373 url: NETDATA.gauge_js,
5376 xhrFields: { withCredentials: true } // required for the cookie
5379 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5382 NETDATA.chartLibraries.gauge.enabled = false;
5383 NETDATA.error(100, NETDATA.gauge_js);
5385 .always(function() {
5386 if(typeof callback === "function")
5391 NETDATA.chartLibraries.gauge.enabled = false;
5392 if(typeof callback === "function")
5397 NETDATA.gaugeAnimation = function(state, status) {
5400 if(typeof status === 'boolean' && status === false)
5402 else if(typeof status === 'number')
5405 // console.log('gauge speed ' + speed);
5406 state.gauge_instance.animationSpeed = speed;
5407 state.___gaugeOld__.speed = speed;
5410 NETDATA.gaugeSet = function(state, value, min, max) {
5411 if(typeof value !== 'number') value = 0;
5412 if(typeof min !== 'number') min = 0;
5413 if(typeof max !== 'number') max = 0;
5414 if(value > max) max = value;
5415 if(value < min) min = value;
5421 else if(min === max)
5424 // gauge.js has an issue if the needle
5425 // is smaller than min or larger than max
5426 // when we set the new values
5427 // the needle will go crazy
5429 // to prevent it, we always feed it
5430 // with a percentage, so that the needle
5431 // is always between min and max
5432 var pcent = (value - min) * 100 / (max - min);
5434 // these should never happen
5435 if(pcent < 0) pcent = 0;
5436 if(pcent > 100) pcent = 100;
5438 state.gauge_instance.set(pcent);
5439 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5441 state.___gaugeOld__.value = value;
5442 state.___gaugeOld__.min = min;
5443 state.___gaugeOld__.max = max;
5446 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5447 if(state.___gaugeOld__.valueLabel !== value) {
5448 state.___gaugeOld__.valueLabel = value;
5449 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5451 if(state.___gaugeOld__.minLabel !== min) {
5452 state.___gaugeOld__.minLabel = min;
5453 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5455 if(state.___gaugeOld__.maxLabel !== max) {
5456 state.___gaugeOld__.maxLabel = max;
5457 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5461 NETDATA.gaugeClearSelection = function(state) {
5462 if(typeof state.gaugeEvent !== 'undefined') {
5463 if(state.gaugeEvent.timer !== undefined) {
5464 clearTimeout(state.gaugeEvent.timer);
5467 state.gaugeEvent.timer = undefined;
5470 if(state.isAutoRefreshable() === true && state.data !== null) {
5471 NETDATA.gaugeChartUpdate(state, state.data);
5474 NETDATA.gaugeAnimation(state, false);
5475 NETDATA.gaugeSet(state, null, null, null);
5476 NETDATA.gaugeSetLabels(state, null, null, null);
5479 NETDATA.gaugeAnimation(state, true);
5483 NETDATA.gaugeSetSelection = function(state, t) {
5484 if(state.timeIsVisible(t) !== true)
5485 return NETDATA.gaugeClearSelection(state);
5487 var slot = state.calculateRowForTime(t);
5488 if(slot < 0 || slot >= state.data.result.length)
5489 return NETDATA.gaugeClearSelection(state);
5491 if(typeof state.gaugeEvent === 'undefined') {
5492 state.gaugeEvent = {
5500 var value = state.data.result[state.data.result.length - 1 - slot];
5501 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5502 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5504 // make sure it is zero based
5505 if(min > 0) min = 0;
5506 if(max < 0) max = 0;
5508 state.gaugeEvent.value = value;
5509 state.gaugeEvent.min = min;
5510 state.gaugeEvent.max = max;
5511 NETDATA.gaugeSetLabels(state, value, min, max);
5513 if(state.gaugeEvent.timer === undefined) {
5514 NETDATA.gaugeAnimation(state, false);
5516 state.gaugeEvent.timer = setTimeout(function() {
5517 state.gaugeEvent.timer = undefined;
5518 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5519 }, NETDATA.options.current.charts_selection_animation_delay);
5525 NETDATA.gaugeChartUpdate = function(state, data) {
5526 var value, min, max;
5528 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5532 NETDATA.gaugeSetLabels(state, null, null, null);
5535 value = data.result[0];
5536 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5537 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5538 if(value < min) min = value;
5539 if(value > max) max = value;
5541 // make sure it is zero based
5542 if(min > 0) min = 0;
5543 if(max < 0) max = 0;
5545 NETDATA.gaugeSetLabels(state, value, min, max);
5548 NETDATA.gaugeSet(state, value, min, max);
5552 NETDATA.gaugeChartCreate = function(state, data) {
5553 var self = $(state.element);
5554 // var chart = $(state.element_chart);
5556 var value = data.result[0];
5557 var min = self.data('gauge-min-value') || null;
5558 var max = self.data('gauge-max-value') || null;
5559 var adjust = self.data('gauge-adjust') || null;
5560 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5561 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5562 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5563 var stopColor = self.data('gauge-stop-color') || void 0;
5564 var generateGradient = self.data('gauge-generate-gradient') || false;
5567 min = NETDATA.commonMin.get(state);
5568 state.gaugeMin = null;
5571 state.gaugeMin = min;
5574 max = NETDATA.commonMax.get(state);
5575 state.gaugeMax = null;
5578 state.gaugeMax = max;
5580 // make sure it is zero based
5581 if(min > 0) min = 0;
5582 if(max < 0) max = 0;
5584 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5586 // case 'width': width = height * ratio; break;
5588 // default: height = width / ratio; break;
5590 //state.element.style.width = width.toString() + 'px';
5591 //state.element.style.height = height.toString() + 'px';
5596 lines: 12, // The number of lines to draw
5597 angle: 0.15, // The length of each line
5598 lineWidth: 0.44, // 0.44 The line thickness
5600 length: 0.8, // 0.9 The radius of the inner circle
5601 strokeWidth: 0.035, // The rotation offset
5602 color: pointerColor // Fill color
5604 colorStart: startColor, // Colors
5605 colorStop: stopColor, // just experiment with them
5606 strokeColor: strokeColor, // to see which ones work best for you
5608 generateGradient: (generateGradient === true),
5612 if (generateGradient.constructor === Array) {
5614 // data-gauge-generate-gradient="[0, 50, 100]"
5615 // data-gauge-gradient-percent-color-0="#FFFFFF"
5616 // data-gauge-gradient-percent-color-50="#999900"
5617 // data-gauge-gradient-percent-color-100="#000000"
5619 options.percentColors = [];
5620 var len = generateGradient.length;
5622 var pcent = generateGradient[len];
5623 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5624 if(color !== false) {
5628 options.percentColors.unshift(a);
5631 if(options.percentColors.length === 0)
5632 delete options.percentColors;
5634 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5635 //noinspection PointlessArithmeticExpressionJS
5636 options.percentColors = [
5637 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5638 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5639 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5640 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5641 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5642 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5643 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5644 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5645 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5646 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5647 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5650 state.gauge_canvas = document.createElement('canvas');
5651 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5652 state.gauge_canvas.className = 'gaugeChart';
5653 state.gauge_canvas.width = width;
5654 state.gauge_canvas.height = height;
5655 state.element_chart.appendChild(state.gauge_canvas);
5657 var valuefontsize = Math.floor(height / 6);
5658 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5659 state.gaugeChartLabel = document.createElement('span');
5660 state.gaugeChartLabel.className = 'gaugeChartLabel';
5661 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5662 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5663 state.element_chart.appendChild(state.gaugeChartLabel);
5665 var titlefontsize = Math.round(valuefontsize / 2);
5667 state.gaugeChartTitle = document.createElement('span');
5668 state.gaugeChartTitle.className = 'gaugeChartTitle';
5669 state.gaugeChartTitle.innerText = state.title;
5670 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5671 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5672 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5673 state.element_chart.appendChild(state.gaugeChartTitle);
5675 var unitfontsize = Math.round(titlefontsize * 0.9);
5676 state.gaugeChartUnits = document.createElement('span');
5677 state.gaugeChartUnits.className = 'gaugeChartUnits';
5678 state.gaugeChartUnits.innerText = state.units;
5679 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5680 state.element_chart.appendChild(state.gaugeChartUnits);
5682 state.gaugeChartMin = document.createElement('span');
5683 state.gaugeChartMin.className = 'gaugeChartMin';
5684 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5685 state.element_chart.appendChild(state.gaugeChartMin);
5687 state.gaugeChartMax = document.createElement('span');
5688 state.gaugeChartMax.className = 'gaugeChartMax';
5689 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5690 state.element_chart.appendChild(state.gaugeChartMax);
5692 // when we just re-create the chart
5693 // do not animate the first update
5695 if(typeof state.gauge_instance !== 'undefined')
5698 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5700 state.___gaugeOld__ = {
5709 // we will always feed a percentage
5710 state.gauge_instance.minValue = 0;
5711 state.gauge_instance.maxValue = 100;
5713 NETDATA.gaugeAnimation(state, animate);
5714 NETDATA.gaugeSet(state, value, min, max);
5715 NETDATA.gaugeSetLabels(state, value, min, max);
5716 NETDATA.gaugeAnimation(state, true);
5720 // ----------------------------------------------------------------------------------------------------------------
5721 // Charts Libraries Registration
5723 NETDATA.chartLibraries = {
5725 initialize: NETDATA.dygraphInitialize,
5726 create: NETDATA.dygraphChartCreate,
5727 update: NETDATA.dygraphChartUpdate,
5728 resize: function(state) {
5729 if(typeof state.dygraph_instance.resize === 'function')
5730 state.dygraph_instance.resize();
5732 setSelection: NETDATA.dygraphSetSelection,
5733 clearSelection: NETDATA.dygraphClearSelection,
5734 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5737 format: function(state) { void(state); return 'json'; },
5738 options: function(state) { void(state); return 'ms|flip'; },
5739 legend: function(state) {
5740 return (this.isSparkline(state) === false)?'right-side':null;
5742 autoresize: function(state) { void(state); return true; },
5743 max_updates_to_recreate: function(state) { void(state); return 5000; },
5744 track_colors: function(state) { void(state); return true; },
5745 pixels_per_point: function(state) {
5746 return (this.isSparkline(state) === false)?3:2;
5748 isSparkline: function(state) {
5749 if(typeof state.dygraph_sparkline === 'undefined') {
5750 var t = $(state.element).data('dygraph-theme');
5751 state.dygraph_sparkline = (t === 'sparkline');
5753 return state.dygraph_sparkline;
5757 initialize: NETDATA.sparklineInitialize,
5758 create: NETDATA.sparklineChartCreate,
5759 update: NETDATA.sparklineChartUpdate,
5761 setSelection: undefined, // function(state, t) { void(state); return true; },
5762 clearSelection: undefined, // function(state) { void(state); return true; },
5763 toolboxPanAndZoom: null,
5766 format: function(state) { void(state); return 'array'; },
5767 options: function(state) { void(state); return 'flip|abs'; },
5768 legend: function(state) { void(state); return null; },
5769 autoresize: function(state) { void(state); return false; },
5770 max_updates_to_recreate: function(state) { void(state); return 5000; },
5771 track_colors: function(state) { void(state); return false; },
5772 pixels_per_point: function(state) { void(state); return 3; }
5775 initialize: NETDATA.peityInitialize,
5776 create: NETDATA.peityChartCreate,
5777 update: NETDATA.peityChartUpdate,
5779 setSelection: undefined, // function(state, t) { void(state); return true; },
5780 clearSelection: undefined, // function(state) { void(state); return true; },
5781 toolboxPanAndZoom: null,
5784 format: function(state) { void(state); return 'ssvcomma'; },
5785 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5786 legend: function(state) { void(state); return null; },
5787 autoresize: function(state) { void(state); return false; },
5788 max_updates_to_recreate: function(state) { void(state); return 5000; },
5789 track_colors: function(state) { void(state); return false; },
5790 pixels_per_point: function(state) { void(state); return 3; }
5793 initialize: NETDATA.morrisInitialize,
5794 create: NETDATA.morrisChartCreate,
5795 update: NETDATA.morrisChartUpdate,
5797 setSelection: undefined, // function(state, t) { void(state); return true; },
5798 clearSelection: undefined, // function(state) { void(state); return true; },
5799 toolboxPanAndZoom: null,
5802 format: function(state) { void(state); return 'json'; },
5803 options: function(state) { void(state); return 'objectrows|ms'; },
5804 legend: function(state) { void(state); return null; },
5805 autoresize: function(state) { void(state); return false; },
5806 max_updates_to_recreate: function(state) { void(state); return 50; },
5807 track_colors: function(state) { void(state); return false; },
5808 pixels_per_point: function(state) { void(state); return 15; }
5811 initialize: NETDATA.googleInitialize,
5812 create: NETDATA.googleChartCreate,
5813 update: NETDATA.googleChartUpdate,
5815 setSelection: undefined, //function(state, t) { void(state); return true; },
5816 clearSelection: undefined, //function(state) { void(state); return true; },
5817 toolboxPanAndZoom: null,
5820 format: function(state) { void(state); return 'datatable'; },
5821 options: function(state) { void(state); return ''; },
5822 legend: function(state) { void(state); return null; },
5823 autoresize: function(state) { void(state); return false; },
5824 max_updates_to_recreate: function(state) { void(state); return 300; },
5825 track_colors: function(state) { void(state); return false; },
5826 pixels_per_point: function(state) { void(state); return 4; }
5829 initialize: NETDATA.raphaelInitialize,
5830 create: NETDATA.raphaelChartCreate,
5831 update: NETDATA.raphaelChartUpdate,
5833 setSelection: undefined, // function(state, t) { void(state); return true; },
5834 clearSelection: undefined, // function(state) { void(state); return true; },
5835 toolboxPanAndZoom: null,
5838 format: function(state) { void(state); return 'json'; },
5839 options: function(state) { void(state); return ''; },
5840 legend: function(state) { void(state); return null; },
5841 autoresize: function(state) { void(state); return false; },
5842 max_updates_to_recreate: function(state) { void(state); return 5000; },
5843 track_colors: function(state) { void(state); return false; },
5844 pixels_per_point: function(state) { void(state); return 3; }
5847 initialize: NETDATA.c3Initialize,
5848 create: NETDATA.c3ChartCreate,
5849 update: NETDATA.c3ChartUpdate,
5851 setSelection: undefined, // function(state, t) { void(state); return true; },
5852 clearSelection: undefined, // function(state) { void(state); return true; },
5853 toolboxPanAndZoom: null,
5856 format: function(state) { void(state); return 'csvjsonarray'; },
5857 options: function(state) { void(state); return 'milliseconds'; },
5858 legend: function(state) { void(state); return null; },
5859 autoresize: function(state) { void(state); return false; },
5860 max_updates_to_recreate: function(state) { void(state); return 5000; },
5861 track_colors: function(state) { void(state); return false; },
5862 pixels_per_point: function(state) { void(state); return 15; }
5865 initialize: NETDATA.d3Initialize,
5866 create: NETDATA.d3ChartCreate,
5867 update: NETDATA.d3ChartUpdate,
5869 setSelection: undefined, // function(state, t) { void(state); return true; },
5870 clearSelection: undefined, // function(state) { void(state); return true; },
5871 toolboxPanAndZoom: null,
5874 format: function(state) { void(state); return 'json'; },
5875 options: function(state) { void(state); return ''; },
5876 legend: function(state) { void(state); return null; },
5877 autoresize: function(state) { void(state); return false; },
5878 max_updates_to_recreate: function(state) { void(state); return 5000; },
5879 track_colors: function(state) { void(state); return false; },
5880 pixels_per_point: function(state) { void(state); return 3; }
5883 initialize: NETDATA.easypiechartInitialize,
5884 create: NETDATA.easypiechartChartCreate,
5885 update: NETDATA.easypiechartChartUpdate,
5887 setSelection: NETDATA.easypiechartSetSelection,
5888 clearSelection: NETDATA.easypiechartClearSelection,
5889 toolboxPanAndZoom: null,
5892 format: function(state) { void(state); return 'array'; },
5893 options: function(state) { void(state); return 'absolute'; },
5894 legend: function(state) { void(state); return null; },
5895 autoresize: function(state) { void(state); return false; },
5896 max_updates_to_recreate: function(state) { void(state); return 5000; },
5897 track_colors: function(state) { void(state); return true; },
5898 pixels_per_point: function(state) { void(state); return 3; },
5902 initialize: NETDATA.gaugeInitialize,
5903 create: NETDATA.gaugeChartCreate,
5904 update: NETDATA.gaugeChartUpdate,
5906 setSelection: NETDATA.gaugeSetSelection,
5907 clearSelection: NETDATA.gaugeClearSelection,
5908 toolboxPanAndZoom: null,
5911 format: function(state) { void(state); return 'array'; },
5912 options: function(state) { void(state); return 'absolute'; },
5913 legend: function(state) { void(state); return null; },
5914 autoresize: function(state) { void(state); return false; },
5915 max_updates_to_recreate: function(state) { void(state); return 5000; },
5916 track_colors: function(state) { void(state); return true; },
5917 pixels_per_point: function(state) { void(state); return 3; },
5922 NETDATA.registerChartLibrary = function(library, url) {
5923 if(NETDATA.options.debug.libraries === true)
5924 console.log("registering chart library: " + library);
5926 NETDATA.chartLibraries[library].url = url;
5927 NETDATA.chartLibraries[library].initialized = true;
5928 NETDATA.chartLibraries[library].enabled = true;
5931 // ----------------------------------------------------------------------------------------------------------------
5932 // Load required JS libraries and CSS
5934 NETDATA.requiredJs = [
5936 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5938 isAlreadyLoaded: function() {
5939 // check if bootstrap is loaded
5940 if(typeof $().emulateTransitionEnd === 'function')
5943 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5948 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5949 isAlreadyLoaded: function() { return false; }
5953 NETDATA.requiredCSS = [
5955 url: NETDATA.themes.current.bootstrap_css,
5956 isAlreadyLoaded: function() {
5957 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5961 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5962 isAlreadyLoaded: function() { return false; }
5965 url: NETDATA.themes.current.dashboard_css,
5966 isAlreadyLoaded: function() { return false; }
5970 NETDATA.loadedRequiredJs = 0;
5971 NETDATA.loadRequiredJs = function(index, callback) {
5972 if(index >= NETDATA.requiredJs.length) {
5973 if(typeof callback === 'function')
5978 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5979 NETDATA.loadedRequiredJs++;
5980 NETDATA.loadRequiredJs(++index, callback);
5984 if(NETDATA.options.debug.main_loop === true)
5985 console.log('loading ' + NETDATA.requiredJs[index].url);
5988 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5992 url: NETDATA.requiredJs[index].url,
5995 xhrFields: { withCredentials: true } // required for the cookie
5998 if(NETDATA.options.debug.main_loop === true)
5999 console.log('loaded ' + NETDATA.requiredJs[index].url);
6002 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6004 .always(function() {
6005 NETDATA.loadedRequiredJs++;
6008 NETDATA.loadRequiredJs(++index, callback);
6012 NETDATA.loadRequiredJs(++index, callback);
6015 NETDATA.loadRequiredCSS = function(index) {
6016 if(index >= NETDATA.requiredCSS.length)
6019 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6020 NETDATA.loadRequiredCSS(++index);
6024 if(NETDATA.options.debug.main_loop === true)
6025 console.log('loading ' + NETDATA.requiredCSS[index].url);
6027 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6028 NETDATA.loadRequiredCSS(++index);
6032 // ----------------------------------------------------------------------------------------------------------------
6033 // Registry of netdata hosts
6036 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6037 chart_div_offset: 100, // give that space above the chart when scrolling to it
6038 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6039 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6041 ms_penalty: 0, // the time penalty of the next alarm
6042 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6043 // if alarms are shown faster than: one per 500ms
6045 notifications: false, // when true, the browser supports notifications (may not be granted though)
6046 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6047 first_notification_id: 0, // the id of the first alarm_log entry for this session
6048 // this is used to prevent CLEAR notifications for past events
6049 // notifications_shown: [],
6051 server: null, // the server to connect to for fetching alarms
6052 current: null, // the list of raised alarms - updated in the background
6053 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6055 notify: function(entry) {
6056 // console.log('alarm ' + entry.unique_id);
6058 if(entry.updated === true) {
6059 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6063 var value_string = entry.value_string;
6065 if(NETDATA.alarms.current !== null) {
6066 // get the current value_string
6067 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6068 if(typeof t !== 'undefined' && entry.status === t.status)
6069 value_string = t.value_string;
6072 var name = entry.name.replace(/_/g, ' ');
6073 var status = entry.status.toLowerCase();
6074 var title = name + ' = ' + value_string.toString();
6075 var tag = entry.alarm_id;
6076 var icon = 'images/seo-performance-128.png';
6077 var interaction = false;
6081 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6083 switch(entry.status) {
6091 case 'UNINITIALIZED':
6095 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6096 // console.log('alarm ' + entry.unique_id + ' is not current');
6099 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6100 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6103 if(entry.no_clear_notification === true) {
6104 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6107 title = name + ' back to normal (' + value_string.toString() + ')';
6108 icon = 'images/check-mark-2-128-green.png';
6109 interaction = false;
6113 if(entry.old_status === 'CRITICAL')
6114 status = 'demoted to ' + entry.status.toLowerCase();
6116 icon = 'images/alert-128-orange.png';
6117 interaction = false;
6121 if(entry.old_status === 'WARNING')
6122 status = 'escalated to ' + entry.status.toLowerCase();
6124 icon = 'images/alert-128-red.png';
6129 console.log('invalid alarm status ' + entry.status);
6134 // cleanup old notifications with the same alarm_id as this one
6135 // FIXME: it does not seem to work on any web browser!
6136 var len = NETDATA.alarms.notifications_shown.length;
6138 var n = NETDATA.alarms.notifications_shown[len];
6139 if(n.data.alarm_id === entry.alarm_id) {
6140 console.log('removing old alarm ' + n.data.unique_id);
6142 // close the notification
6145 // remove it from the array
6146 NETDATA.alarms.notifications_shown.splice(len, 1);
6147 len = NETDATA.alarms.notifications_shown.length;
6154 setTimeout(function() {
6155 // show this notification
6156 // console.log('new notification: ' + title);
6157 var n = new Notification(title, {
6158 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6160 requireInteraction: interaction,
6161 icon: NETDATA.serverDefault + icon,
6165 n.onclick = function(event) {
6166 event.preventDefault();
6167 NETDATA.alarms.onclick(event.target.data);
6171 // NETDATA.alarms.notifications_shown.push(n);
6172 // console.log(entry);
6173 }, NETDATA.alarms.ms_penalty);
6175 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6179 scrollToChart: function(chart_id) {
6180 if(typeof chart_id === 'string') {
6181 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6182 if(typeof offset !== 'undefined') {
6183 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6190 scrollToAlarm: function(alarm) {
6191 if(typeof alarm === 'object') {
6192 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6194 if(ret === true && NETDATA.options.page_is_visible === false)
6196 // 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.');
6201 notifyAll: function() {
6202 // console.log('FETCHING ALARM LOG');
6203 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6204 // console.log('ALARM LOG FETCHED');
6206 if(data === null || typeof data !== 'object') {
6207 console.log('invalid alarms log response');
6211 if(data.length === 0) {
6212 console.log('received empty alarm log');
6216 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6218 data.sort(function(a, b) {
6219 if(a.unique_id > b.unique_id) return -1;
6220 if(a.unique_id < b.unique_id) return 1;
6224 NETDATA.alarms.ms_penalty = 0;
6226 var len = data.length;
6228 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6229 NETDATA.alarms.notify(data[len]);
6232 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6235 NETDATA.alarms.last_notification_id = data[0].unique_id;
6236 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6237 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6241 check_notifications: function() {
6242 // returns true if we should fire 1+ notifications
6244 if(NETDATA.alarms.notifications !== true) {
6245 // console.log('notifications not available');
6249 if(Notification.permission !== 'granted') {
6250 // console.log('notifications not granted');
6254 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6255 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6257 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6258 // console.log('new alarms detected');
6261 //else console.log('no new alarms');
6263 // else console.log('cannot process alarms');
6268 get: function(what, callback) {
6270 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6274 'Cache-Control': 'no-cache, no-store',
6275 'Pragma': 'no-cache'
6277 xhrFields: { withCredentials: true } // required for the cookie
6279 .done(function(data) {
6280 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6281 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6283 if(typeof callback === 'function')
6284 return callback(data);
6287 NETDATA.error(415, NETDATA.alarms.server);
6289 if(typeof callback === 'function')
6290 return callback(null);
6294 update_forever: function() {
6295 NETDATA.alarms.get('active', function(data) {
6297 NETDATA.alarms.current = data;
6299 if(NETDATA.alarms.check_notifications() === true) {
6300 NETDATA.alarms.notifyAll();
6303 if (typeof NETDATA.alarms.callback === 'function') {
6304 NETDATA.alarms.callback(data);
6307 // Health monitoring is disabled on this netdata
6308 if(data.status === false) return;
6311 setTimeout(NETDATA.alarms.update_forever, 10000);
6315 get_log: function(last_id, callback) {
6316 // console.log('fetching all log after ' + last_id.toString());
6318 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6322 'Cache-Control': 'no-cache, no-store',
6323 'Pragma': 'no-cache'
6325 xhrFields: { withCredentials: true } // required for the cookie
6327 .done(function(data) {
6328 if(typeof callback === 'function')
6329 return callback(data);
6332 NETDATA.error(416, NETDATA.alarms.server);
6334 if(typeof callback === 'function')
6335 return callback(null);
6340 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6342 NETDATA.alarms.last_notification_id =
6343 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6345 if(NETDATA.alarms.onclick === null)
6346 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6348 if(netdataShowAlarms === true) {
6349 NETDATA.alarms.update_forever();
6351 if('Notification' in window) {
6352 // console.log('notifications available');
6353 NETDATA.alarms.notifications = true;
6355 if(Notification.permission === 'default')
6356 Notification.requestPermission();
6362 // ----------------------------------------------------------------------------------------------------------------
6363 // Registry of netdata hosts
6365 NETDATA.registry = {
6366 server: null, // the netdata registry server
6367 person_guid: null, // the unique ID of this browser / user
6368 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6369 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6370 machines: null, // the user's other URLs
6371 machines_array: null, // the user's other URLs in an array
6374 parsePersonUrls: function(person_urls) {
6375 // console.log(person_urls);
6376 NETDATA.registry.person_urls = person_urls;
6379 NETDATA.registry.machines = {};
6380 NETDATA.registry.machines_array = [];
6382 var apu = person_urls;
6385 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6386 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6392 accesses: apu[i][3],
6396 obj.alternate_urls.push(apu[i][1]);
6398 NETDATA.registry.machines[apu[i][0]] = obj;
6399 NETDATA.registry.machines_array.push(obj);
6402 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6404 var pu = NETDATA.registry.machines[apu[i][0]];
6405 if(pu.last_t < apu[i][2]) {
6407 pu.last_t = apu[i][2];
6408 pu.name = apu[i][4];
6410 pu.accesses += apu[i][3];
6411 pu.alternate_urls.push(apu[i][1]);
6416 if(typeof netdataRegistryCallback === 'function')
6417 netdataRegistryCallback(NETDATA.registry.machines_array);
6421 if(netdataRegistry !== true) return;
6423 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6425 NETDATA.registry.server = data.registry;
6426 NETDATA.registry.machine_guid = data.machine_guid;
6428 if(typeof data.hostname === 'string')
6429 NETDATA.registry.hostname = data.hostname;
6431 NETDATA.registry.access(2, function (person_urls) {
6432 NETDATA.registry.parsePersonUrls(person_urls);
6439 hello: function(host, callback) {
6440 host = NETDATA.fixHost(host);
6442 // send HELLO to a netdata server:
6443 // 1. verifies the server is reachable
6444 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6446 url: host + '/api/v1/registry?action=hello',
6450 'Cache-Control': 'no-cache, no-store',
6451 'Pragma': 'no-cache'
6453 xhrFields: { withCredentials: true } // required for the cookie
6455 .done(function(data) {
6456 if(typeof data.status !== 'string' || data.status !== 'ok') {
6457 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6461 if(typeof callback === 'function')
6462 return callback(data);
6465 NETDATA.error(407, host);
6467 if(typeof callback === 'function')
6468 return callback(null);
6472 access: function(max_redirects, callback) {
6473 // send ACCESS to a netdata registry:
6474 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6475 // 2. it responds with a list of netdata servers we know
6476 // the registry identifies us using a cookie it sets the first time we access it
6477 // the registry may respond with a redirect URL to send us to another registry
6479 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),
6483 'Cache-Control': 'no-cache, no-store',
6484 'Pragma': 'no-cache'
6486 xhrFields: { withCredentials: true } // required for the cookie
6488 .done(function(data) {
6489 var redirect = null;
6490 if(typeof data.registry === 'string')
6491 redirect = data.registry;
6493 if(typeof data.status !== 'string' || data.status !== 'ok') {
6494 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6499 if(redirect !== null && max_redirects > 0) {
6500 NETDATA.registry.server = redirect;
6501 NETDATA.registry.access(max_redirects - 1, callback);
6504 if(typeof callback === 'function')
6505 return callback(null);
6509 if(typeof data.person_guid === 'string')
6510 NETDATA.registry.person_guid = data.person_guid;
6512 if(typeof callback === 'function')
6513 return callback(data.urls);
6517 NETDATA.error(410, NETDATA.registry.server);
6519 if(typeof callback === 'function')
6520 return callback(null);
6524 delete: function(delete_url, callback) {
6525 // send DELETE to a netdata registry:
6527 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),
6531 'Cache-Control': 'no-cache, no-store',
6532 'Pragma': 'no-cache'
6534 xhrFields: { withCredentials: true } // required for the cookie
6536 .done(function(data) {
6537 if(typeof data.status !== 'string' || data.status !== 'ok') {
6538 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6542 if(typeof callback === 'function')
6543 return callback(data);
6546 NETDATA.error(412, NETDATA.registry.server);
6548 if(typeof callback === 'function')
6549 return callback(null);
6553 search: function(machine_guid, callback) {
6554 // SEARCH for the URLs of a machine:
6556 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,
6560 'Cache-Control': 'no-cache, no-store',
6561 'Pragma': 'no-cache'
6563 xhrFields: { withCredentials: true } // required for the cookie
6565 .done(function(data) {
6566 if(typeof data.status !== 'string' || data.status !== 'ok') {
6567 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6571 if(typeof callback === 'function')
6572 return callback(data);
6575 NETDATA.error(418, NETDATA.registry.server);
6577 if(typeof callback === 'function')
6578 return callback(null);
6582 switch: function(new_person_guid, callback) {
6585 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,
6589 'Cache-Control': 'no-cache, no-store',
6590 'Pragma': 'no-cache'
6592 xhrFields: { withCredentials: true } // required for the cookie
6594 .done(function(data) {
6595 if(typeof data.status !== 'string' || data.status !== 'ok') {
6596 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6600 if(typeof callback === 'function')
6601 return callback(data);
6604 NETDATA.error(414, NETDATA.registry.server);
6606 if(typeof callback === 'function')
6607 return callback(null);
6612 // ----------------------------------------------------------------------------------------------------------------
6615 if(typeof netdataPrepCallback === 'function')
6616 netdataPrepCallback();
6618 NETDATA.errorReset();
6619 NETDATA.loadRequiredCSS(0);
6621 NETDATA._loadjQuery(function() {
6622 NETDATA.loadRequiredJs(0, function() {
6623 if(typeof $().emulateTransitionEnd !== 'function') {
6624 // bootstrap is not available
6625 NETDATA.options.current.show_help = false;
6628 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6629 if(NETDATA.options.debug.main_loop === true)
6630 console.log('starting chart refresh thread');
6636 })(window, document);